From 849df98e55dd35389cc2fd55935d1592bd2692f6 Mon Sep 17 00:00:00 2001
From: Ryan Morshead <ryan.morshead@gmail.com>
Date: Tue, 18 Apr 2023 18:05:27 -0700
Subject: [PATCH 01/37] Update pull_request_template.md

---
 .github/pull_request_template.md | 11 ++++++++---
 1 file changed, 8 insertions(+), 3 deletions(-)

diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index a119e699c..a423818ca 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -1,7 +1,12 @@
-## Checklist
+## Issues
+
+<!-- Link the issues this change resolves, or describe them -->
+
+## Summary
 
-Please update this checklist as you complete each item:
+<!-- Describe how these changes resolve the aforementioned issues -->
+
+## Checklist
 
 - [ ] Tests have been included for all bug fixes or added functionality.
 - [ ] The `changelog.rst` has been updated with any significant changes.
-- [ ] GitHub Issues which may be closed by this Pull Request have been linked.

From 46bdf8c33ce19275ae8c7be729e2fb13a6754beb Mon Sep 17 00:00:00 2001
From: Ryan Morshead <ryan.morshead@gmail.com>
Date: Sun, 23 Apr 2023 00:18:32 -0600
Subject: [PATCH 02/37] WSGIContainer.environ changed (#971)

Specifically it changed from a staticmethod to an instance one.
This happened in Tornado 6.3. See:
https://github.com/tornadoweb/tornado/pull/3231#issuecomment-1518957578
---
 src/reactpy/backend/tornado.py | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/src/reactpy/backend/tornado.py b/src/reactpy/backend/tornado.py
index c2a034ab2..b79f5fa0e 100644
--- a/src/reactpy/backend/tornado.py
+++ b/src/reactpy/backend/tornado.py
@@ -196,7 +196,7 @@ async def recv() -> Any:
                     ConnectionContext(
                         self._component_constructor(),
                         value=Connection(
-                            scope=WSGIContainer.environ(self.request),
+                            scope=_FAKE_WSGI_CONTAINER.environ(self.request),
                             location=Location(
                                 pathname=f"/{path[len(self._url_prefix):]}",
                                 search=(
@@ -222,3 +222,10 @@ async def on_message(self, message: str | bytes) -> None:
     def on_close(self) -> None:
         if not self._dispatch_future.done():
             self._dispatch_future.cancel()
+
+
+# The interface for WSGIContainer.environ changed in Tornado version 6.3 from
+# a staticmethod to an instance method. Since we're not that concerned with
+# the details of the WSGI app itself, we can just use a fake one.
+# see: https://github.com/tornadoweb/tornado/pull/3231#issuecomment-1518957578
+_FAKE_WSGI_CONTAINER = WSGIContainer(lambda *a, **kw: iter([]))

From 0f34eb8904abb6ebfd6a098308f794ab3340cf6b Mon Sep 17 00:00:00 2001
From: Gautam Sharma <64241254+ZEUS-03@users.noreply.github.com>
Date: Mon, 24 Apr 2023 01:34:30 +0530
Subject: [PATCH 03/37] added warning for html.script (#970)

* added warning for html.script

* updated Disclaimer to warning

* warning added under scripting section

* improve warning description

* improve warning placement

---------

Co-authored-by: Ryan Morshead <ryan.morshead@gmail.com>
---
 src/reactpy/html.py | 50 ++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 49 insertions(+), 1 deletion(-)

diff --git a/src/reactpy/html.py b/src/reactpy/html.py
index fa4953cda..5c1d8b6ad 100644
--- a/src/reactpy/html.py
+++ b/src/reactpy/html.py
@@ -1,4 +1,5 @@
 """
+
 **Fragment**
 
 - :func:`_`
@@ -409,7 +410,12 @@ def _script(
     key: Key | None,
     event_handlers: EventHandlerDict,
 ) -> VdomDict:
-    """Create a new `<{script}> <https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script>`__ element.
+    """Create a new `<script> <https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script>`__ element.
+
+    .. warning::
+
+        Be careful to sanitize data from untrusted sources before using it in a script.
+        See the "Notes" for more details
 
     This behaves slightly differently than a normal script element in that it may be run
     multiple times if its key changes (depending on specific browser behaviors). If no
@@ -421,6 +427,48 @@ def _script(
     content of the script changes. The function may itself optionally return a teardown
     function that is called when the script element is removed from the tree, or when
     the script content changes.
+
+    Notes:
+        Do not use unsanitized data from untrusted sources anywhere in your script.
+        Doing so may allow for malicious code injection. Consider this **insecure**
+        code:
+
+        .. code-block::
+
+            my_script = html.script(f"console.log('{user_bio}');")
+
+        A clever attacker could construct ``user_bio`` such that they could escape the
+        string and execute arbitrary code to perform cross-site scripting
+        (`XSS <https://en.wikipedia.org/wiki/Cross-site_scripting>`__`). For example,
+        what if ``user_bio`` were of the form:
+
+        .. code-block:: text
+
+            '); attackerCodeHere(); ('
+
+        This would allow the following Javascript code to be executed client-side:
+
+        .. code-block:: js
+
+            console.log(''); attackerCodeHere(); ('');
+
+        One way to avoid this could be to escape ``user_bio`` so as to prevent the
+        injection of Javascript code. For example:
+
+        .. code-block:: python
+
+            import json
+            my_script = html.script(f"console.log({json.dumps(user_bio)});")
+
+        This would prevent the injection of Javascript code by escaping the ``user_bio``
+        string. In this case, the following client-side code would be executed instead:
+
+        .. code-block:: js
+
+            console.log("'); attackerCodeHere(); ('");
+
+        This is a very simple example, but it illustrates the point that you should
+        always be careful when using unsanitized data from untrusted sources.
     """
     model: VdomDict = {"tagName": "script"}
 

From 9ffaf93fbd9ebf8fb953e3ab994f8c7e05bd9bd9 Mon Sep 17 00:00:00 2001
From: rmorshea <ryan.morshead@gmail.com>
Date: Sat, 29 Apr 2023 22:08:36 -0600
Subject: [PATCH 04/37] use newly released reactpy-flake8

---
 pyproject.toml               | 2 +-
 requirements/check-style.txt | 2 +-
 src/reactpy/future.py        | 0
 3 files changed, 2 insertions(+), 2 deletions(-)
 create mode 100644 src/reactpy/future.py

diff --git a/pyproject.toml b/pyproject.toml
index d912115a7..f843140ea 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -55,7 +55,7 @@ per-file-ignores = [
 ]
 max-line-length = 88
 max-complexity = 20
-select = ["B", "C", "E", "F", "W", "T4", "B9", "N", "ROH"]
+select = ["B", "C", "E", "F", "W", "T4", "B9", "N", "RPY"]
 exclude = ["**/node_modules/*", ".eggs/*", ".tox/*"]
 # -- flake8-tidy-imports --
 ban-relative-imports = "true"
diff --git a/requirements/check-style.txt b/requirements/check-style.txt
index eff7b3164..035e52138 100644
--- a/requirements/check-style.txt
+++ b/requirements/check-style.txt
@@ -1,7 +1,7 @@
 black[jupyter]
 flake8
 flake8-pyproject
-flake8-idom-hooks >=0.5.0
+reactpy-flake8 >=0.7
 flake8-print
 flake8-tidy-imports
 isort >=5.7.0
diff --git a/src/reactpy/future.py b/src/reactpy/future.py
new file mode 100644
index 000000000..e69de29bb

From 472d83134c388aa5b2af1b9c25f846827d0cc672 Mon Sep 17 00:00:00 2001
From: Ryan Morshead <ryan.morshead@gmail.com>
Date: Mon, 15 May 2023 11:38:17 -0700
Subject: [PATCH 05/37] Use span instead of div (#974)

Div effect how the page is displayed.
---
 src/client/packages/@reactpy/client/src/components.tsx | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/client/packages/@reactpy/client/src/components.tsx b/src/client/packages/@reactpy/client/src/components.tsx
index de134682a..728c4cec7 100644
--- a/src/client/packages/@reactpy/client/src/components.tsx
+++ b/src/client/packages/@reactpy/client/src/components.tsx
@@ -161,12 +161,12 @@ function ImportedElement({ model }: { model: ReactPyVdom }) {
     if (!importSourceFallback) {
       return null;
     } else if (typeof importSourceFallback === "string") {
-      return <div>{importSourceFallback}</div>;
+      return <span>{importSourceFallback}</span>;
     } else {
       return <StandardElement model={importSourceFallback} />;
     }
   } else {
-    return <div ref={importSourceRef} />;
+    return <span ref={importSourceRef} />;
   }
 }
 

From 07f55218f7221931ebb7ddd906594e83b75040a0 Mon Sep 17 00:00:00 2001
From: Ryan Morshead <ryan.morshead@gmail.com>
Date: Sat, 20 May 2023 14:34:49 -0700
Subject: [PATCH 06/37] fix js template links (#979)

---
 docs/source/about/contributor-guide.rst                    | 7 +++----
 .../guides/escape-hatches/distributing-javascript.rst      | 2 +-
 .../source/guides/escape-hatches/javascript-components.rst | 2 +-
 3 files changed, 5 insertions(+), 6 deletions(-)

diff --git a/docs/source/about/contributor-guide.rst b/docs/source/about/contributor-guide.rst
index 79cd2fb4c..e26d89e52 100644
--- a/docs/source/about/contributor-guide.rst
+++ b/docs/source/about/contributor-guide.rst
@@ -305,10 +305,10 @@ Other Core Repositories
 ReactPy depends on, or is used by several other core projects. For documentation on them
 you should refer to their respective documentation in the links below:
 
-- `reactpy-react-component-cookiecutter
-  <https://github.com/reactive-python/reactpy-react-component-cookiecutter>`__ - Template repo
+- `reactpy-js-component-template
+  <https://github.com/reactive-python/reactpy-js-component-template>`__ - Template repo
   for making :ref:`Custom Javascript Components`.
-- `flake8-reactpy-hooks <https://github.com/reactive-python/flake8-reactpy-hooks>`__ - Enforces the
+- `reactpy-flake8 <https://github.com/reactive-python/reactpy-flake8>`__ - Enforces the
   :ref:`Rules of Hooks`
 - `reactpy-jupyter <https://github.com/reactive-python/reactpy-jupyter>`__ - ReactPy integration for
   Jupyter
@@ -321,7 +321,6 @@ you should refer to their respective documentation in the links below:
 .. =====
 
 .. _Google Chrome: https://www.google.com/chrome/
-.. _ChromeDriver: https://chromedriver.chromium.org/downloads
 .. _Docker: https://docs.docker.com/get-docker/
 .. _Git: https://git-scm.com/book/en/v2/Getting-Started-Installing-Git
 .. _Git Bash: https://gitforwindows.org/
diff --git a/docs/source/guides/escape-hatches/distributing-javascript.rst b/docs/source/guides/escape-hatches/distributing-javascript.rst
index a92166291..0028cee36 100644
--- a/docs/source/guides/escape-hatches/distributing-javascript.rst
+++ b/docs/source/guides/escape-hatches/distributing-javascript.rst
@@ -297,7 +297,7 @@ up and running as quickly as possible.
 .. _install NPM: https://www.npmjs.com/get-npm
 .. _CDN: https://en.wikipedia.org/wiki/Content_delivery_network
 .. _PyPI: https://pypi.org/
-.. _template repository: https://github.com/reactive-python/reactpy-react-component-cookiecutter
+.. _template repository: https://github.com/reactive-python/reactpy-js-component-template
 .. _web module: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules
 .. _Rollup: https://rollupjs.org/guide/en/
 .. _Webpack: https://webpack.js.org/
diff --git a/docs/source/guides/escape-hatches/javascript-components.rst b/docs/source/guides/escape-hatches/javascript-components.rst
index f66ca60fb..f0a71b6b7 100644
--- a/docs/source/guides/escape-hatches/javascript-components.rst
+++ b/docs/source/guides/escape-hatches/javascript-components.rst
@@ -140,7 +140,7 @@ like `JSX <https://reactjs.org/docs/introducing-jsx.html>`__ and instead will us
 
 .. _Material UI: https://material-ui.com/
 .. _CDN: https://en.wikipedia.org/wiki/Content_delivery_network
-.. _template repository: https://github.com/reactive-python/reactpy-react-component-cookiecutter
+.. _template repository: https://github.com/reactive-python/reactpy-js-component-template
 .. _web module: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules
 .. _Rollup: https://rollupjs.org/guide/en/
 .. _Webpack: https://webpack.js.org/

From c86c59d4aaafb2dc40db0d8b4b2ad07f3f15dacb Mon Sep 17 00:00:00 2001
From: Ryan Morshead <ryan.morshead@gmail.com>
Date: Tue, 23 May 2023 21:07:51 -0600
Subject: [PATCH 07/37] doc improvements (#984)

* fix js template links

* better HTML docs
---
 docs/source/_custom_js/package-lock.json      |   4 +-
 .../html-with-reactpy/index.rst               |  19 +-
 .../rendering-data/index.rst                  |  14 +-
 docs/source/index.rst                         |   1 +
 docs/source/reference/html-attributes.rst     | 197 ++++++++++++++++++
 5 files changed, 220 insertions(+), 15 deletions(-)
 create mode 100644 docs/source/reference/html-attributes.rst

diff --git a/docs/source/_custom_js/package-lock.json b/docs/source/_custom_js/package-lock.json
index f69eb9c1c..fc00fcb83 100644
--- a/docs/source/_custom_js/package-lock.json
+++ b/docs/source/_custom_js/package-lock.json
@@ -19,7 +19,7 @@
       }
     },
     "../../../src/client/packages/@reactpy/client": {
-      "version": "0.2.1",
+      "version": "0.3.1",
       "integrity": "sha512-pIK5eNwFSHKXg7ClpASWFVKyZDYxz59MSFpVaX/OqJFkrJaAxBuhKGXNTMXmuyWOL5Iyvb/ErwwDRxQRzMNkfQ==",
       "license": "MIT",
       "dependencies": {
@@ -30,7 +30,6 @@
         "@types/json-pointer": "^1.0.31",
         "@types/react": "^17.0",
         "@types/react-dom": "^17.0",
-        "prettier": "^3.0.0-alpha.6",
         "typescript": "^4.9.5"
       },
       "peerDependencies": {
@@ -442,7 +441,6 @@
         "@types/react-dom": "^17.0",
         "event-to-object": "^0.1.2",
         "json-pointer": "^0.6.2",
-        "prettier": "^3.0.0-alpha.6",
         "typescript": "^4.9.5"
       }
     },
diff --git a/docs/source/guides/creating-interfaces/html-with-reactpy/index.rst b/docs/source/guides/creating-interfaces/html-with-reactpy/index.rst
index bdc249204..99a973d94 100644
--- a/docs/source/guides/creating-interfaces/html-with-reactpy/index.rst
+++ b/docs/source/guides/creating-interfaces/html-with-reactpy/index.rst
@@ -78,22 +78,20 @@ to specify a URL to its ``src`` and use some ``style`` to modify and position it
 
     <img
         src="https://picsum.photos/id/237/500/300"
+        class="img-fluid"
         style="width: 50%; margin-left: 25%;"
         alt="Billie Holiday"
         tabindex="0"
     />
 
-In ReactPy we add these attributes to elements using keyword arguments. There are two main
-notable differences though. First, all names in ReactPy use ``snake_case`` instead of
-dash-separated words. For example, ``tabindex`` and ``margin-left`` become ``tab_index``
-and ``margin_left`` respectively. Second, instead of specifying ``style`` using a
-string, we use a dictionary. Given this, you can rewrite the ``<img>`` element above as:
+In ReactPy we add these attributes to elements using a dictionary:
 
 .. testcode::
 
     html.img(
         {
             "src": "https://picsum.photos/id/237/500/300",
+            "class_name": "img-fluid",
             "style": {"width": "50%", "margin_left": "25%"},
             "alt": "Billie Holiday",
         }
@@ -104,10 +102,21 @@ string, we use a dictionary. Given this, you can rewrite the ``<img>`` element a
     <!-- no tabindex since that would ruin accesibility of the page -->
     <img
         src="https://picsum.photos/id/237/500/300"
+        class="img-fluid"
         style="width: 50%; margin-left: 25%;"
         alt="Billie Holiday"
     />
 
+There are some notable differences. First, all names in ReactPy use ``snake_case`` instead
+of dash-separated words. For example, ``tabindex`` and ``margin-left`` become
+``tab_index`` and ``margin_left`` respectively. Second, instead of using a string to
+specify the ``style`` attribute, we use a dictionary to describe the CSS properties we
+want to apply to an element. This is done to avoid having to escape quotes and other
+characters in the string. Finally, the ``class`` attribute is renamed to ``class_name``
+to avoid conflicting with the ``class`` keyword in Python.
+
+For full list of supported attributes and differences from HTML, see the
+:ref:`HTML Attributes` reference.
 
 ----------
 
diff --git a/docs/source/guides/creating-interfaces/rendering-data/index.rst b/docs/source/guides/creating-interfaces/rendering-data/index.rst
index 29013717d..8b11ac439 100644
--- a/docs/source/guides/creating-interfaces/rendering-data/index.rst
+++ b/docs/source/guides/creating-interfaces/rendering-data/index.rst
@@ -80,10 +80,6 @@ With this we can now imaging writing some filtering and sorting logic using Pyth
 displaying items whose ``priority`` is less than or equal to some ``filter_by_priority``
 and then ordering the elements based on the ``priority``:
 
-.. testcode::
-
-    x = 1
-
 .. testcode::
 
     filter_by_priority = 1
@@ -105,6 +101,11 @@ and then ordering the elements based on the ``priority``:
 
 We could then add this code to our ``DataList`` component:
 
+.. warning::
+
+    The code below produces a bunch of warnings! Be sure to tead the
+    :ref:`next section <Organizing Items With Keys>` to find out why.
+
 .. reactpy:: _examples/sorted_and_filtered_todo_list
 
 
@@ -135,12 +136,11 @@ structure even further to include a unique ID for each item in our todo list:
         {"id": 7, "text": "Read a book", "priority": 1},
     ]
 
-Then, as we're constructing our ``<li>`` elements we'll pass in a ``key`` argument to
-the element constructor:
+Then, as we're constructing our ``<li>`` elements we'll declare a ``key`` attribute:
 
 .. code-block::
 
-    list_item_elements = [html.li(t["text"], key=t["id"]) for t in tasks]
+    list_item_elements = [html.li({"key": t["id"]}, t["text"]) for t in tasks]
 
 This ``key`` tells ReactPy which ``<li>`` element corresponds to which item of data in our
 ``tasks`` list. This becomes important if the order or number of items in your list can
diff --git a/docs/source/index.rst b/docs/source/index.rst
index 712db3ead..8b21160f6 100644
--- a/docs/source/index.rst
+++ b/docs/source/index.rst
@@ -23,6 +23,7 @@ ReactPy
     :caption: Reference
 
     reference/browser-events
+    reference/html-attributes
     reference/hooks-api
     _auto/apis
     reference/javascript-api
diff --git a/docs/source/reference/html-attributes.rst b/docs/source/reference/html-attributes.rst
new file mode 100644
index 000000000..25530b033
--- /dev/null
+++ b/docs/source/reference/html-attributes.rst
@@ -0,0 +1,197 @@
+.. testcode::
+
+    from reactpy import html
+
+
+HTML Attributes
+===============
+
+In ReactPy, HTML attributes are specified using snake_case instead of dash-separated
+words. For example, ``tabindex`` and ``margin-left`` become ``tab_index`` and
+``margin_left`` respectively.
+
+
+Noteable Attributes
+-------------------
+
+Some attributes in ReactPy are renamed, have special meaning, or are used differently
+than in HTML.
+
+``style``
+.........
+
+As mentioned above, instead of using a string to specify the ``style`` attribute, we use
+a dictionary to describe the CSS properties we want to apply to an element. For example,
+the following HTML:
+
+.. code-block:: html
+
+    <div style="width: 50%; margin-left: 25%;">
+        <h1 style="margin-top: 0px;">My Todo List</h1>
+        <ul>
+            <li>Build a cool new app</li>
+            <li>Share it with the world!</li>
+        </ul>
+    </div>
+
+Would be written in ReactPy as:
+
+.. testcode::
+
+    html.div(
+        {
+            "style": {
+                "width": "50%",
+                "margin_left": "25%",
+            },
+        },
+        html.h1(
+            {
+                "style": {
+                    "margin_top": "0px",
+                },
+            },
+            "My Todo List",
+        ),
+        html.ul(
+            html.li("Build a cool new app"),
+            html.li("Share it with the world!"),
+        ),
+    )
+
+``class`` vs ``class_name``
+...........................
+
+In HTML, the ``class`` attribute is used to specify a CSS class for an element. In
+ReactPy, this attribute is renamed to ``class_name`` to avoid conflicting with the
+``class`` keyword in Python. For example, the following HTML:
+
+.. code-block:: html
+
+    <div class="container">
+        <h1 class="title">My Todo List</h1>
+        <ul class="list">
+            <li class="item">Build a cool new app</li>
+            <li class="item">Share it with the world!</li>
+        </ul>
+    </div>
+
+Would be written in ReactPy as:
+
+.. testcode::
+
+    html.div(
+        {"class_name": "container"},
+        html.h1({"class_name": "title"}, "My Todo List"),
+        html.ul(
+            {"class_name": "list"},
+            html.li({"class_name": "item"}, "Build a cool new app"),
+            html.li({"class_name": "item"}, "Share it with the world!"),
+        ),
+    )
+
+``for`` vs ``html_for``
+.......................
+
+In HTML, the ``for`` attribute is used to specify the ``id`` of the element it's
+associated with. In ReactPy, this attribute is renamed to ``html_for`` to avoid
+conflicting with the ``for`` keyword in Python. For example, the following HTML:
+
+.. code-block:: html
+
+    <div>
+        <label for="todo">Todo:</label>
+        <input id="todo" type="text" />
+    </div>
+
+Would be written in ReactPy as:
+
+.. testcode::
+
+    html.div(
+        html.label({"html_for": "todo"}, "Todo:"),
+        html.input({"id": "todo", "type": "text"}),
+    )
+
+``dangerously_set_inner_HTML``
+..............................
+
+This is used to set the ``innerHTML`` property of an element and should be provided a
+dictionary with a single key ``__html`` whose value is the HTML to be set. It should be
+used with **extreme caution** as it can lead to XSS attacks if the HTML inside isn't
+trusted (for example if it comes from user input).
+
+
+All Attributes
+--------------
+
+`access_key <https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/accesskey>`__
+  A string. Specifies a keyboard shortcut for the element. Not generally recommended.
+
+`aria_* <https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes>`__
+  ARIA attributes let you specify the accessibility tree information for this element.
+  See ARIA attributes for a complete reference. In ReactPr, all ARIA attribute names are
+  exactly the same as in HTML.
+
+`auto_capitalize <https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/autocapitalize>`__
+  A string. Specifies whether and how the user input should be capitalized.
+
+`content_editable <https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/contenteditable>`__
+  A boolean. If true, the browser lets the user edit the rendered element directly. This
+  is used to implement rich text input libraries like Lexical. ReactPr warns if you try
+  to pass children to an element with ``content_editable = True`` because ReactPy will
+  not be able to update its content after user edits.
+
+`data_* <https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/data-*>`__
+  Data attributes let you attach some string data to the element, for example
+  data-fruit="banana". In ReactPy, they are not commonly used because you would usually
+  read data from props or state instead.
+
+`dir <https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/dir>`__
+  Either ``"ltr"`` or ``"rtl"``. Specifies the text direction of the element.
+
+`draggable <https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/draggable>`__
+  A boolean. Specifies whether the element is draggable. Part of HTML Drag and Drop API.
+
+`enter_key_hint <https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/enterkeyhint>`__
+  A string. Specifies which action to present for the enter key on virtual keyboards.
+
+`hidden <https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/hidden>`__
+  A boolean or a string. Specifies whether the element should be hidden.
+
+- `id <https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/id>`__:
+  A string. Specifies a unique identifier for this element, which can be used to find it
+  later or connect it with other elements. Generate it with useId to avoid clashes
+  between multiple instances of the same component.
+
+`is <https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-is>`__
+  A string. If specified, the component will behave like a custom element.
+
+`input_mode <https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/inputmode>`__
+  A string. Specifies what kind of keyboard to display (for example, text, number, or telephone).
+
+`item_prop <https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/itemprop>`__
+  A string. Specifies which property the element represents for structured data crawlers.
+
+`lang <https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/lang>`__
+  A string. Specifies the language of the element.
+
+`role <https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/role>`__
+  A string. Specifies the element role explicitly for assistive technologies.
+
+`slot <https://developer.mozilla.org/en-US/docs/Web/HTML/Element/slot>`__
+  A string. Specifies the slot name when using shadow DOM. In ReactPy, an equivalent
+  pattern is typically achieved by passing JSX as props, for example
+  ``<Layout left={<Sidebar />} right={<Content />} />``.
+
+`spell_check <https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/spellcheck>`__
+  A boolean or null. If explicitly set to true or false, enables or disables spellchecking.
+
+`tab_index <https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/tabindex>`__
+  A number. Overrides the default Tab button behavior. Avoid using values other than -1 and 0.
+
+`title <https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/title>`__
+  A string. Specifies the tooltip text for the element.
+
+`translate <https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/translate>`__
+  Either 'yes' or 'no'. Passing 'no' excludes the element content from being translated.

From daf910267ed31b247cf4d5dcb085bc90c60dbae6 Mon Sep 17 00:00:00 2001
From: Ryan Morshead <ryan.morshead@gmail.com>
Date: Wed, 24 May 2023 22:58:18 -0600
Subject: [PATCH 08/37] docstrings for backend hooks (#994)

---
 src/reactpy/backend/hooks.py | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/src/reactpy/backend/hooks.py b/src/reactpy/backend/hooks.py
index f2dce5501..981461316 100644
--- a/src/reactpy/backend/hooks.py
+++ b/src/reactpy/backend/hooks.py
@@ -12,6 +12,7 @@
 
 
 def use_connection() -> Connection[Any]:
+    """Get the current :class:`~reactpy.backend.types.Connection`."""
     conn = use_context(ConnectionContext)
     if conn is None:
         raise RuntimeError("No backend established a connection.")  # pragma: no cover
@@ -19,8 +20,10 @@ def use_connection() -> Connection[Any]:
 
 
 def use_scope() -> MutableMapping[str, Any]:
+    """Get the current :class:`~reactpy.backend.types.Connection`'s scope."""
     return use_connection().scope
 
 
 def use_location() -> Location:
+    """Get the current :class:`~reactpy.backend.types.Connection`'s location."""
     return use_connection().location

From cf7950d4689b0f1c83b528bcf9fa4c53be863523 Mon Sep 17 00:00:00 2001
From: Mark <16909269+Archmonger@users.noreply.github.com>
Date: Sun, 28 May 2023 21:55:11 -0700
Subject: [PATCH 09/37] Clean up readme (#1000)

---
 README.md | 44 ++++++++++++++++++++++++--------------------
 1 file changed, 24 insertions(+), 20 deletions(-)

diff --git a/README.md b/README.md
index ead7f7f91..015249125 100644
--- a/README.md
+++ b/README.md
@@ -1,23 +1,26 @@
-<p align="center" id="reactpy">
-    <a href="#">
-        <img src="https://raw.githubusercontent.com/reactive-python/reactpy/main/branding/svg/reactpy-logo-landscape.svg" alt="ReactPy Logo" style="min-width: 300px; width: 50%" />
-    </a>
-</p>
-<p align="center">
-    <em>Reactive user interfaces with pure Python</em>
-</p>
-    <p align="center">
+# <img src="https://raw.githubusercontent.com/reactive-python/reactpy/main/branding/svg/reactpy-logo-square.svg" align="left" height="45"/> ReactPy
+
+<p>
     <a href="https://github.com/reactive-python/reactpy/actions">
-        <img src="https://github.com/reactive-python/reactpy/workflows/test/badge.svg" alt="Build Status">
+        <img src="https://github.com/reactive-python/reactpy/workflows/test/badge.svg?event=push">
     </a>
     <a href="https://pypi.org/project/reactpy/">
-        <img src="https://badge.fury.io/py/reactpy.svg" alt="Package version">
+        <img src="https://img.shields.io/pypi/v/reactpy.svg?label=PyPI">
+    </a>
+    <a href="https://github.com/reactive-python/reactpy/blob/main/LICENSE">
+        <img src="https://img.shields.io/badge/License-MIT-purple.svg">
+    </a>
+    <a href="https://reactpy.dev/">
+        <img src="https://img.shields.io/website?down_message=offline&label=Docs&logo=read-the-docs&logoColor=white&up_message=online&url=https%3A%2F%2Freactpy.dev%2Fdocs%2Findex.html">
+    </a>
+    <a href="https://discord.gg/uNb5P4hA9X">
+        <img src="https://img.shields.io/discord/1111078259854168116?label=Discord&logo=discord">
     </a>
 </p>
 
 ---
 
-[ReactPy](https://reactpy.dev/) is a library for building user interfaces in Python without Javascript. ReactPy interfaces are made from components which look and behave similarly to those found in [ReactJS](https://reactjs.org/). Designed with simplicity in mind, ReactPy can be used by those without web development experience while also being powerful enough to grow with your ambitions.
+[ReactPy](https://reactpy.dev/) is a library for building user interfaces in Python without Javascript. ReactPy interfaces are made from components that look and behave similar to those found in [ReactJS](https://reactjs.org/). Designed with simplicity in mind, ReactPy can be used by those without web development experience while also being powerful enough to grow with your ambitions.
 
 <table align="center">
     <thead>
@@ -38,7 +41,7 @@
         <td>
             <a href="https://github.com/reactive-python/reactpy-django">Django</a>,
             <a href="https://github.com/reactive-python/reactpy-jupyter">Jupyter</a>,
-            <a href="https://github.com/reactive-python/reactpy-dash">Plotly-Dash</a>
+            <a href="https://github.com/idom-team/idom-dash">Plotly-Dash</a>
         </td>
         </tr>
     </tbody>
@@ -52,18 +55,19 @@ To get a rough idea of how to write apps in ReactPy, take a look at this tiny _H
 from reactpy import component, html, run
 
 @component
-def HelloWorld():
+def hello_world():
     return html.h1("Hello, World!")
 
-run(HelloWorld)
+run(hello_world)
 ```
 
 # Resources
 
 Follow the links below to find out more about this project.
 
--   [Try it Now](https://mybinder.org/v2/gh/reactive-python/reactpy-jupyter/main?urlpath=lab/tree/notebooks/introduction.ipynb) - check out ReactPy in a Jupyter Notebook.
--   [Documentation](https://reactpy.dev/) - learn how to install, run, and use ReactPy.
--   [Community Forum](https://github.com/reactive-python/reactpy/discussions) - ask questions, share ideas, and show off projects.
--   [Contributor Guide](https://reactpy.dev/docs/developing-reactpy/contributor-guide.html) - see how you can help develop this project.
--   [Code of Conduct](https://github.com/reactive-python/reactpy/blob/main/CODE_OF_CONDUCT.md) - standards for interacting with this community.
+-   [Try ReactPy (Jupyter Notebook)](https://mybinder.org/v2/gh/reactive-python/reactpy-jupyter/main?urlpath=lab/tree/notebooks/introduction.ipynb)
+-   [Documentation](https://reactpy.dev/)
+-   [GitHub Discussions](https://github.com/reactive-python/reactpy/discussions)
+-   [Discord](https://discord.gg/uNb5P4hA9X)
+-   [Contributor Guide](https://reactpy.dev/docs/developing-reactpy/contributor-guide.html)
+-   [Code of Conduct](https://github.com/reactive-python/reactpy/blob/main/CODE_OF_CONDUCT.md)

From ac582666eb93e44f54d8ab05d7bfff1edb2cf0b7 Mon Sep 17 00:00:00 2001
From: Ryan Morshead <ryan.morshead@gmail.com>
Date: Sat, 3 Jun 2023 15:56:52 -0600
Subject: [PATCH 10/37] Poetry+Hatch Monorepo (#1002)

* re-organize dirs + start using hatch

* setup root pyproject.toml + basic invoke tasks

* add publish task

* more ruff fixes

* get workflows to run

* split up script runs

* rename to check

* change matrix order

* make ruff happy

* get tests to pass

* check semver

* more fixes

* ignore missing coverage

* fix cov

* fix import sort

* try build in env-js

* try latest hatch-build-scripts

* misc fixes

* try to fix npm in gh action

* do not set registry url by default

* allow re-runs

* no need for extra build

* fix doc build and tests

* remove scripts

* fix tests

* update contributor guide
---
 .../{.nox-session.yml => .hatch-run.yml}      |   23 +-
 .github/workflows/check.yml                   |   45 +
 .github/workflows/publish.yml                 |    5 +-
 .github/workflows/test.yml                    |   37 -
 .gitignore                                    |    3 -
 .pre-commit-config.yaml                       |   14 -
 docs/Dockerfile                               |   42 +-
 docs/Makefile                                 |   19 -
 docs/{ => docs_app}/__init__.py               |    0
 docs/{ => docs_app}/app.py                    |   15 +-
 scripts/live_docs.py => docs/docs_app/dev.py  |   17 +-
 docs/{ => docs_app}/examples.py               |    7 +-
 docs/docs_app/prod.py                         |   14 +
 docs/main.py                                  |    9 +
 docs/make.bat                                 |   35 -
 docs/poetry.lock                              | 2269 +++++++++++++++++
 docs/pyproject.toml                           |   23 +
 docs/source/_custom_js/package-lock.json      |   25 +-
 docs/source/_custom_js/package.json           |    2 +-
 docs/source/_exts/async_doctest.py            |    3 -
 docs/source/_exts/autogen_api_docs.py         |   14 +-
 docs/source/_exts/build_custom_js.py          |    5 +-
 docs/source/_exts/custom_autosectionlabel.py  |    1 -
 docs/source/_exts/reactpy_example.py          |   20 +-
 docs/source/_exts/reactpy_view.py             |    7 +-
 docs/source/about/contributor-guide.rst       |   78 +-
 docs/source/conf.py                           |   21 +-
 .../_examples/adding_state_variable/main.py   |    1 -
 .../_examples/isolated_state/main.py          |    1 -
 .../multiple_state_variables/main.py          |    1 -
 .../_examples/list_re_order.py                |    2 +-
 .../sorted_and_filtered_todo_list.py          |    2 +-
 .../_examples/todo_list_with_keys.py          |    2 +-
 .../_examples/material_ui_button_no_action.py |    1 -
 .../_examples/material_ui_button_on_click.py  |    1 -
 .../_examples/super_simple_chart/main.py      |    1 -
 .../_examples/debug_error_example.py          |    3 +-
 .../getting-started/_examples/run_fastapi.py  |    1 -
 .../getting-started/_examples/run_flask.py    |    1 -
 .../getting-started/_examples/run_sanic.py    |    1 -
 .../_examples/run_starlette.py                |    1 -
 .../getting-started/_examples/run_tornado.py  |    1 -
 .../getting-started/_examples/sample_app.py   |    1 -
 .../_static/embed-reactpy-view/main.py        |    1 -
 .../_examples/filterable_list/main.py         |    8 +-
 .../_examples/character_movement/main.py      |    1 -
 .../reference/_examples/material_ui_switch.py |    1 -
 .../reference/_examples/matplotlib_plot.py    |    6 +-
 .../reference/_examples/network_graph.py      |    1 -
 .../source/reference/_examples/pigeon_maps.py |   21 +-
 .../reference/_examples/simple_dashboard.py   |    1 -
 docs/source/reference/_examples/snake_game.py |    2 +-
 docs/source/reference/_examples/todo.py       |    2 +-
 .../_examples/use_reducer_counter.py          |    3 +-
 .../reference/_examples/victory_chart.py      |    1 -
 noxfile.py                                    |  553 ----
 pyproject.toml                                |  183 +-
 requirements/build-docs.txt                   |    9 -
 requirements/build-pkg.txt                    |    3 -
 requirements/check-style.txt                  |   10 -
 requirements/check-types.txt                  |    6 -
 requirements/make-release.txt                 |    1 -
 requirements/nox-deps.txt                     |    2 -
 requirements/pkg-deps.txt                     |    9 -
 requirements/pkg-extras.txt                   |   24 -
 requirements/test-env.txt                     |   15 -
 scripts/README.md                             |    5 -
 scripts/one_example.py                        |  103 -
 scripts/run_docs.py                           |   20 -
 setup.py                                      |  203 --
 src/{client => js}/.eslintrc.json             |    0
 src/{client => js}/.gitignore                 |    1 +
 src/{client => js}/README.md                  |    0
 src/{client/ui => js/app}/.eslintrc.json      |    0
 src/{client/ui => js/app}/index.html          |    0
 src/{client/ui => js/app}/package-lock.json   |    0
 src/{client/ui => js/app}/package.json        |    1 -
 .../app}/public/assets/reactpy-logo.ico       |  Bin
 src/{client/ui => js/app}/src/index.ts        |    0
 src/{client/ui => js/app}/tsconfig.json       |    0
 src/{client/ui => js/app}/vite.config.js      |    2 +-
 src/{client => js}/package-lock.json          |  214 +-
 src/{client => js}/package.json               |   10 +-
 .../packages/@reactpy/client/.gitignore       |    0
 .../packages/@reactpy/client/README.md        |    0
 .../packages/@reactpy/client/package.json     |    0
 .../@reactpy/client/src/components.tsx        |    0
 .../packages/@reactpy/client/src/index.ts     |    0
 .../packages/@reactpy/client/src/logger.ts    |    0
 .../packages/@reactpy/client/src/messages.ts  |    0
 .../packages/@reactpy/client/src/mount.tsx    |    0
 .../@reactpy/client/src/reactpy-client.ts     |    0
 .../@reactpy/client/src/reactpy-vdom.tsx      |    0
 .../packages/@reactpy/client/tsconfig.json    |    0
 .../packages/event-to-object/package.json     |    0
 .../packages/event-to-object/src/events.ts    |    0
 .../packages/event-to-object/src/index.ts     |    0
 .../tests/event-to-object.test.ts             |    0
 .../event-to-object/tests/tooling/check.ts    |    0
 .../event-to-object/tests/tooling/mock.ts     |    0
 .../event-to-object/tests/tooling/setup.js    |    0
 .../packages/event-to-object/tsconfig.json    |    0
 .../event-to-object/tsconfig.tests.json       |    0
 src/{client => js}/tsconfig.package.json      |    0
 src/py/reactpy/.gitignore                     |    4 +
 MANIFEST.in => src/py/reactpy/MANIFEST.in     |    0
 src/py/reactpy/README.md                      |   23 +
 src/py/reactpy/pyproject.toml                 |  175 ++
 src/{ => py/reactpy}/reactpy/__init__.py      |    1 -
 src/{ => py/reactpy}/reactpy/__main__.py      |    0
 .../reactpy}/reactpy/_console/__init__.py     |    0
 .../reactpy}/reactpy/_console/ast_utils.py    |   19 +-
 .../_console/rewrite_camel_case_props.py      |    6 +-
 .../reactpy}/reactpy/_console/rewrite_keys.py |    5 +-
 src/{ => py/reactpy}/reactpy/_option.py       |   13 +-
 src/{ => py/reactpy}/reactpy/_warnings.py     |    5 +-
 .../reactpy}/reactpy/backend/__init__.py      |    0
 .../reactpy}/reactpy/backend/_common.py       |   14 +-
 .../reactpy}/reactpy/backend/default.py       |   23 +-
 .../reactpy}/reactpy/backend/fastapi.py       |    3 +-
 src/{ => py/reactpy}/reactpy/backend/flask.py |   36 +-
 src/{ => py/reactpy}/reactpy/backend/hooks.py |   12 +-
 src/{ => py/reactpy}/reactpy/backend/sanic.py |   32 +-
 .../reactpy}/reactpy/backend/starlette.py     |   34 +-
 .../reactpy}/reactpy/backend/tornado.py       |   30 +-
 src/{ => py/reactpy}/reactpy/backend/types.py |    6 +-
 src/{ => py/reactpy}/reactpy/backend/utils.py |   19 +-
 src/{ => py/reactpy}/reactpy/config.py        |    3 +-
 src/{ => py/reactpy}/reactpy/core/__init__.py |    0
 src/{ => py/reactpy}/reactpy/core/_f_back.py  |    7 +-
 .../reactpy}/reactpy/core/_thread_local.py    |    1 -
 .../reactpy}/reactpy/core/component.py        |   17 +-
 src/{ => py/reactpy}/reactpy/core/events.py   |   18 +-
 src/{ => py/reactpy}/reactpy/core/hooks.py    |   35 +-
 src/{ => py/reactpy}/reactpy/core/layout.py   |   84 +-
 src/{ => py/reactpy}/reactpy/core/serve.py    |   14 +-
 src/{ => py/reactpy}/reactpy/core/types.py    |   34 +-
 src/{ => py/reactpy}/reactpy/core/vdom.py     |   18 +-
 src/{ => py/reactpy}/reactpy/future.py        |    0
 src/{ => py/reactpy}/reactpy/html.py          |   21 +-
 src/{ => py/reactpy}/reactpy/logging.py       |    3 +-
 src/{ => py/reactpy}/reactpy/py.typed         |    0
 src/{ => py/reactpy}/reactpy/sample.py        |    6 +-
 src/{ => py/reactpy}/reactpy/svg.py           |    5 +-
 .../reactpy}/reactpy/testing/__init__.py      |   14 +-
 .../reactpy}/reactpy/testing/backend.py       |   41 +-
 .../reactpy}/reactpy/testing/common.py        |   16 +-
 .../reactpy}/reactpy/testing/display.py       |    8 +-
 src/{ => py/reactpy}/reactpy/testing/logs.py  |    5 +-
 src/{ => py/reactpy}/reactpy/types.py         |    9 +-
 src/{ => py/reactpy}/reactpy/utils.py         |   37 +-
 src/{ => py/reactpy}/reactpy/web/__init__.py  |    3 +-
 src/{ => py/reactpy}/reactpy/web/module.py    |   20 +-
 .../reactpy}/reactpy/web/templates/react.js   |    2 +-
 src/{ => py/reactpy}/reactpy/web/utils.py     |   14 +-
 src/{ => py/reactpy}/reactpy/widgets.py       |   22 +-
 {tests => src/py/reactpy/tests}/__init__.py   |    0
 {tests => src/py/reactpy/tests}/conftest.py   |    2 +-
 .../reactpy/tests}/test__console/__init__.py  |    0
 .../test_rewrite_camel_case_props.py          |    1 -
 .../tests}/test__console/test_rewrite_keys.py |    1 -
 .../py/reactpy/tests}/test__option.py         |    2 +-
 .../reactpy/tests}/test_backend/__init__.py   |    0
 .../tests}/test_backend/test__common.py       |    0
 .../reactpy/tests}/test_backend/test_all.py   |    4 +-
 .../reactpy/tests}/test_backend/test_utils.py |    2 +-
 .../py/reactpy/tests}/test_client.py          |    3 +-
 .../py/reactpy/tests}/test_config.py          |    0
 .../py/reactpy/tests}/test_core/__init__.py   |    0
 .../tests}/test_core/test_component.py        |    0
 .../reactpy/tests}/test_core/test_events.py   |    2 +-
 .../py/reactpy/tests}/test_core/test_hooks.py |   85 +-
 .../reactpy/tests}/test_core/test_layout.py   |   36 +-
 .../py/reactpy/tests}/test_core/test_serve.py |    8 +-
 .../py/reactpy/tests}/test_core/test_vdom.py  |    3 +-
 {tests => src/py/reactpy/tests}/test_html.py  |    0
 .../py/reactpy/tests}/test_sample.py          |    0
 .../py/reactpy/tests}/test_testing.py         |   23 +-
 {tests => src/py/reactpy/tests}/test_utils.py |    2 +-
 .../py/reactpy/tests}/test_web/__init__.py    |    0
 .../js_fixtures/component-can-have-child.js   |    6 +-
 .../js_fixtures/export-resolution/index.js    |    2 +
 .../js_fixtures/export-resolution/one.js      |    2 +-
 .../js_fixtures/export-resolution/two.js      |    2 +-
 .../test_web/js_fixtures/exports-syntax.js    |    0
 .../js_fixtures/exports-two-components.js     |    6 +-
 .../set-flag-when-unmount-is-called.js        |    2 +-
 .../test_web/js_fixtures/simple-button.js     |    4 +-
 .../py/reactpy/tests}/test_web/test_module.py |    1 -
 .../py/reactpy/tests}/test_web/test_utils.py  |    1 -
 .../py/reactpy/tests}/test_widgets.py         |    1 -
 .../py/reactpy/tests}/tooling/__init__.py     |    0
 .../py/reactpy/tests}/tooling/asserts.py      |    4 +-
 .../py/reactpy/tests}/tooling/common.py       |    1 -
 .../py/reactpy/tests}/tooling/hooks.py        |    0
 .../py/reactpy/tests}/tooling/loop.py         |   28 +-
 tasks.py                                      |  426 ++++
 .../js_fixtures/export-resolution/index.js    |    2 -
 198 files changed, 3859 insertions(+), 1916 deletions(-)
 rename .github/workflows/{.nox-session.yml => .hatch-run.yml} (82%)
 create mode 100644 .github/workflows/check.yml
 delete mode 100644 .github/workflows/test.yml
 delete mode 100644 .pre-commit-config.yaml
 delete mode 100644 docs/Makefile
 rename docs/{ => docs_app}/__init__.py (100%)
 rename docs/{ => docs_app}/app.py (77%)
 rename scripts/live_docs.py => docs/docs_app/dev.py (88%)
 rename docs/{ => docs_app}/examples.py (97%)
 create mode 100644 docs/docs_app/prod.py
 create mode 100644 docs/main.py
 delete mode 100644 docs/make.bat
 create mode 100644 docs/poetry.lock
 create mode 100644 docs/pyproject.toml
 delete mode 100644 noxfile.py
 delete mode 100644 requirements/build-docs.txt
 delete mode 100644 requirements/build-pkg.txt
 delete mode 100644 requirements/check-style.txt
 delete mode 100644 requirements/check-types.txt
 delete mode 100644 requirements/make-release.txt
 delete mode 100644 requirements/nox-deps.txt
 delete mode 100644 requirements/pkg-deps.txt
 delete mode 100644 requirements/pkg-extras.txt
 delete mode 100644 requirements/test-env.txt
 delete mode 100644 scripts/README.md
 delete mode 100644 scripts/one_example.py
 delete mode 100644 scripts/run_docs.py
 delete mode 100644 setup.py
 rename src/{client => js}/.eslintrc.json (100%)
 rename src/{client => js}/.gitignore (91%)
 rename src/{client => js}/README.md (100%)
 rename src/{client/ui => js/app}/.eslintrc.json (100%)
 rename src/{client/ui => js/app}/index.html (100%)
 rename src/{client/ui => js/app}/package-lock.json (100%)
 rename src/{client/ui => js/app}/package.json (94%)
 rename src/{client/ui => js/app}/public/assets/reactpy-logo.ico (100%)
 rename src/{client/ui => js/app}/src/index.ts (100%)
 rename src/{client/ui => js/app}/tsconfig.json (100%)
 rename src/{client/ui => js/app}/vite.config.js (75%)
 rename src/{client => js}/package-lock.json (98%)
 rename src/{client => js}/package.json (62%)
 rename src/{client => js}/packages/@reactpy/client/.gitignore (100%)
 rename src/{client => js}/packages/@reactpy/client/README.md (100%)
 rename src/{client => js}/packages/@reactpy/client/package.json (100%)
 rename src/{client => js}/packages/@reactpy/client/src/components.tsx (100%)
 rename src/{client => js}/packages/@reactpy/client/src/index.ts (100%)
 rename src/{client => js}/packages/@reactpy/client/src/logger.ts (100%)
 rename src/{client => js}/packages/@reactpy/client/src/messages.ts (100%)
 rename src/{client => js}/packages/@reactpy/client/src/mount.tsx (100%)
 rename src/{client => js}/packages/@reactpy/client/src/reactpy-client.ts (100%)
 rename src/{client => js}/packages/@reactpy/client/src/reactpy-vdom.tsx (100%)
 rename src/{client => js}/packages/@reactpy/client/tsconfig.json (100%)
 rename src/{client => js}/packages/event-to-object/package.json (100%)
 rename src/{client => js}/packages/event-to-object/src/events.ts (100%)
 rename src/{client => js}/packages/event-to-object/src/index.ts (100%)
 rename src/{client => js}/packages/event-to-object/tests/event-to-object.test.ts (100%)
 rename src/{client => js}/packages/event-to-object/tests/tooling/check.ts (100%)
 rename src/{client => js}/packages/event-to-object/tests/tooling/mock.ts (100%)
 rename src/{client => js}/packages/event-to-object/tests/tooling/setup.js (100%)
 rename src/{client => js}/packages/event-to-object/tsconfig.json (100%)
 rename src/{client => js}/packages/event-to-object/tsconfig.tests.json (100%)
 rename src/{client => js}/tsconfig.package.json (100%)
 create mode 100644 src/py/reactpy/.gitignore
 rename MANIFEST.in => src/py/reactpy/MANIFEST.in (100%)
 create mode 100644 src/py/reactpy/README.md
 create mode 100644 src/py/reactpy/pyproject.toml
 rename src/{ => py/reactpy}/reactpy/__init__.py (99%)
 rename src/{ => py/reactpy}/reactpy/__main__.py (100%)
 rename src/{ => py/reactpy}/reactpy/_console/__init__.py (100%)
 rename src/{ => py/reactpy}/reactpy/_console/ast_utils.py (93%)
 rename src/{ => py/reactpy}/reactpy/_console/rewrite_camel_case_props.py (96%)
 rename src/{ => py/reactpy}/reactpy/_console/rewrite_keys.py (96%)
 rename src/{ => py/reactpy}/reactpy/_option.py (91%)
 rename src/{ => py/reactpy}/reactpy/_warnings.py (88%)
 rename src/{ => py/reactpy}/reactpy/backend/__init__.py (100%)
 rename src/{ => py/reactpy}/reactpy/backend/_common.py (91%)
 rename src/{ => py/reactpy}/reactpy/backend/default.py (71%)
 rename src/{ => py/reactpy}/reactpy/backend/fastapi.py (95%)
 rename src/{ => py/reactpy}/reactpy/backend/flask.py (90%)
 rename src/{ => py/reactpy}/reactpy/backend/hooks.py (75%)
 rename src/{ => py/reactpy}/reactpy/backend/sanic.py (92%)
 rename src/{ => py/reactpy}/reactpy/backend/starlette.py (91%)
 rename src/{ => py/reactpy}/reactpy/backend/tornado.py (93%)
 rename src/{ => py/reactpy}/reactpy/backend/types.py (93%)
 rename src/{ => py/reactpy}/reactpy/backend/utils.py (86%)
 rename src/{ => py/reactpy}/reactpy/config.py (97%)
 rename src/{ => py/reactpy}/reactpy/core/__init__.py (100%)
 rename src/{ => py/reactpy}/reactpy/core/_f_back.py (73%)
 rename src/{ => py/reactpy}/reactpy/core/_thread_local.py (99%)
 rename src/{ => py/reactpy}/reactpy/core/component.py (80%)
 rename src/{ => py/reactpy}/reactpy/core/events.py (93%)
 rename src/{ => py/reactpy}/reactpy/core/hooks.py (96%)
 rename src/{ => py/reactpy}/reactpy/core/layout.py (92%)
 rename src/{ => py/reactpy}/reactpy/core/serve.py (85%)
 rename src/{ => py/reactpy}/reactpy/core/types.py (90%)
 rename src/{ => py/reactpy}/reactpy/core/vdom.py (96%)
 rename src/{ => py/reactpy}/reactpy/future.py (100%)
 rename src/{ => py/reactpy}/reactpy/html.py (95%)
 rename src/{ => py/reactpy}/reactpy/logging.py (95%)
 rename src/{ => py/reactpy}/reactpy/py.typed (100%)
 rename src/{ => py/reactpy}/reactpy/sample.py (81%)
 rename src/{ => py/reactpy}/reactpy/svg.py (97%)
 rename src/{ => py/reactpy}/reactpy/testing/__init__.py (59%)
 rename src/{ => py/reactpy}/reactpy/testing/backend.py (87%)
 rename src/{ => py/reactpy}/reactpy/testing/common.py (93%)
 rename src/{ => py/reactpy}/reactpy/testing/display.py (93%)
 rename src/{ => py/reactpy}/reactpy/testing/logs.py (98%)
 rename src/{ => py/reactpy}/reactpy/types.py (81%)
 rename src/{ => py/reactpy}/reactpy/utils.py (90%)
 rename src/{ => py/reactpy}/reactpy/web/__init__.py (87%)
 rename src/{ => py/reactpy}/reactpy/web/module.py (95%)
 rename src/{ => py/reactpy}/reactpy/web/templates/react.js (99%)
 rename src/{ => py/reactpy}/reactpy/web/utils.py (96%)
 rename src/{ => py/reactpy}/reactpy/widgets.py (85%)
 rename {tests => src/py/reactpy/tests}/__init__.py (100%)
 rename {tests => src/py/reactpy/tests}/conftest.py (97%)
 rename {tests => src/py/reactpy/tests}/test__console/__init__.py (100%)
 rename {tests => src/py/reactpy/tests}/test__console/test_rewrite_camel_case_props.py (99%)
 rename {tests => src/py/reactpy/tests}/test__console/test_rewrite_keys.py (99%)
 rename {tests => src/py/reactpy/tests}/test__option.py (98%)
 rename {tests => src/py/reactpy/tests}/test_backend/__init__.py (100%)
 rename {tests => src/py/reactpy/tests}/test_backend/test__common.py (100%)
 rename {tests => src/py/reactpy/tests}/test_backend/test_all.py (97%)
 rename {tests => src/py/reactpy/tests}/test_backend/test_utils.py (95%)
 rename {tests => src/py/reactpy/tests}/test_client.py (98%)
 rename {tests => src/py/reactpy/tests}/test_config.py (100%)
 rename {tests => src/py/reactpy/tests}/test_core/__init__.py (100%)
 rename {tests => src/py/reactpy/tests}/test_core/test_component.py (100%)
 rename {tests => src/py/reactpy/tests}/test_core/test_events.py (99%)
 rename {tests => src/py/reactpy/tests}/test_core/test_hooks.py (97%)
 rename {tests => src/py/reactpy/tests}/test_core/test_layout.py (98%)
 rename {tests => src/py/reactpy/tests}/test_core/test_serve.py (97%)
 rename {tests => src/py/reactpy/tests}/test_core/test_vdom.py (99%)
 rename {tests => src/py/reactpy/tests}/test_html.py (100%)
 rename {tests => src/py/reactpy/tests}/test_sample.py (100%)
 rename {tests => src/py/reactpy/tests}/test_testing.py (91%)
 rename {tests => src/py/reactpy/tests}/test_utils.py (99%)
 rename {tests => src/py/reactpy/tests}/test_web/__init__.py (100%)
 rename {tests => src/py/reactpy/tests}/test_web/js_fixtures/component-can-have-child.js (90%)
 create mode 100644 src/py/reactpy/tests/test_web/js_fixtures/export-resolution/index.js
 rename {tests => src/py/reactpy/tests}/test_web/js_fixtures/export-resolution/one.js (88%)
 rename {tests => src/py/reactpy/tests}/test_web/js_fixtures/export-resolution/two.js (65%)
 rename {tests => src/py/reactpy/tests}/test_web/js_fixtures/exports-syntax.js (100%)
 rename {tests => src/py/reactpy/tests}/test_web/js_fixtures/exports-two-components.js (80%)
 rename {tests => src/py/reactpy/tests}/test_web/js_fixtures/set-flag-when-unmount-is-called.js (99%)
 rename {tests => src/py/reactpy/tests}/test_web/js_fixtures/simple-button.js (95%)
 rename {tests => src/py/reactpy/tests}/test_web/test_module.py (99%)
 rename {tests => src/py/reactpy/tests}/test_web/test_utils.py (99%)
 rename {tests => src/py/reactpy/tests}/test_widgets.py (99%)
 rename {tests => src/py/reactpy/tests}/tooling/__init__.py (100%)
 rename {tests => src/py/reactpy/tests}/tooling/asserts.py (62%)
 rename {tests => src/py/reactpy/tests}/tooling/common.py (99%)
 rename {tests => src/py/reactpy/tests}/tooling/hooks.py (100%)
 rename {tests => src/py/reactpy/tests}/tooling/loop.py (75%)
 create mode 100644 tasks.py
 delete mode 100644 tests/test_web/js_fixtures/export-resolution/index.js

diff --git a/.github/workflows/.nox-session.yml b/.github/workflows/.hatch-run.yml
similarity index 82%
rename from .github/workflows/.nox-session.yml
rename to .github/workflows/.hatch-run.yml
index 827fb4192..b312869e4 100644
--- a/.github/workflows/.nox-session.yml
+++ b/.github/workflows/.hatch-run.yml
@@ -1,4 +1,4 @@
-name: Nox Session
+name: hatch-run
 
 on:
   workflow_call:
@@ -6,12 +6,9 @@ on:
       job-name:
         required: true
         type: string
-      nox-args:
+      hatch-run:
         required: true
         type: string
-      nox-session-args:
-        required: false
-        type: string
       runs-on-array:
         required: false
         type: string
@@ -20,6 +17,10 @@ on:
         required: false
         type: string
         default: '["3.x"]'
+      node-registry-url:
+        required: false
+        type: string
+        default: ""
     secrets:
       node-auth-token:
         required: false
@@ -29,19 +30,19 @@ on:
         required: false
 
 jobs:
-  nox-session:
+  hatch:
     name: ${{ format(inputs.job-name, matrix.python-version, matrix.runs-on) }}
     strategy:
       matrix:
-        runs-on: ${{ fromJson(inputs.runs-on-array) }}
         python-version: ${{ fromJson(inputs.python-version-array) }}
+        runs-on: ${{ fromJson(inputs.runs-on-array) }}
     runs-on: ${{ matrix.runs-on }}
     steps:
       - uses: actions/checkout@v2
       - uses: actions/setup-node@v2
         with:
           node-version: "14.x"
-          registry-url: "https://registry.npmjs.org"
+          registry-url: ${{ inputs.node-registry-url }}
       - name: Pin NPM Version
         run: npm install -g npm@8.19.3
       - name: Use Python ${{ matrix.python-version }}
@@ -49,10 +50,10 @@ jobs:
         with:
           python-version: ${{ matrix.python-version }}
       - name: Install Python Dependencies
-        run: pip install -r requirements/nox-deps.txt
-      - name: Run Sessions
+        run: pip install hatch poetry
+      - name: Run Scripts
         env:
           NODE_AUTH_TOKEN: ${{ secrets.node-auth-token }}
           PYPI_USERNAME: ${{ secrets.pypi-username }}
           PYPI_PASSWORD: ${{ secrets.pypi-password }}
-        run: nox ${{ inputs.nox-args }} --stop-on-first-error -- ${{ inputs.nox-session-args }}
+        run: hatch run ${{ inputs.hatch-run }}
diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml
new file mode 100644
index 000000000..af768579c
--- /dev/null
+++ b/.github/workflows/check.yml
@@ -0,0 +1,45 @@
+name: check
+
+on:
+  push:
+    branches:
+      - main
+  pull_request:
+    branches:
+      - main
+  schedule:
+    - cron: "0 0 * * 0"
+
+jobs:
+  test-py-cov:
+    uses: ./.github/workflows/.hatch-run.yml
+    with:
+      job-name: "python-{0}"
+      hatch-run: "test-py"
+  lint-py:
+    uses: ./.github/workflows/.hatch-run.yml
+    with:
+      job-name: "python-{0}"
+      hatch-run: "lint-py"
+  test-py-matrix:
+    uses: ./.github/workflows/.hatch-run.yml
+    with:
+      job-name: "python-{0} {1}"
+      hatch-run: "test-py --no-cov"
+      runs-on-array: '["ubuntu-latest", "macos-latest", "windows-latest"]'
+      python-version-array: '["3.9", "3.10", "3.11"]'
+  test-docs:
+    uses: ./.github/workflows/.hatch-run.yml
+    with:
+      job-name: "python-{0}"
+      hatch-run: "test-docs"
+  test-js:
+    uses: ./.github/workflows/.hatch-run.yml
+    with:
+      job-name: "{1}"
+      hatch-run: "test-js"
+  lint-js:
+    uses: ./.github/workflows/.hatch-run.yml
+    with:
+      job-name: "{1}"
+      hatch-run: "lint-js"
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 18090e4b0..e9271cbd5 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -9,10 +9,11 @@ on:
 
 jobs:
   publish:
-    uses: ./.github/workflows/.nox-session.yml
+    uses: ./.github/workflows/.hatch-run.yml
     with:
       job-name: "publish"
-      nox-args: "-s publish"
+      hatch-run: "publish"
+      node-registry-url: "https://registry.npmjs.org"
     secrets:
       node-auth-token: ${{ secrets.NODE_AUTH_TOKEN }}
       pypi-username: ${{ secrets.PYPI_USERNAME }}
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
deleted file mode 100644
index 0b733f924..000000000
--- a/.github/workflows/test.yml
+++ /dev/null
@@ -1,37 +0,0 @@
-name: test
-
-on:
-  push:
-    branches:
-      - main
-  pull_request:
-    branches:
-      - main
-  schedule:
-    - cron: "0 0 * * 0"
-
-jobs:
-  python-exhaustive:
-    uses: ./.github/workflows/.nox-session.yml
-    with:
-      job-name: "python-{0}"
-      nox-args: "-t check-python"
-      nox-session-args: "--pytest --maxfail=3 --reruns 3"
-  python-environments:
-    uses: ./.github/workflows/.nox-session.yml
-    with:
-      job-name: "python-{0} {1}"
-      nox-args: "-s check-python-tests"
-      nox-session-args: "--no-cov --pytest --maxfail=3 --reruns 3"
-      runs-on-array: '["ubuntu-latest", "macos-latest", "windows-latest"]'
-      python-version-array: '["3.7", "3.8", "3.9", "3.10", "3.11"]'
-  docs:
-    uses: ./.github/workflows/.nox-session.yml
-    with:
-      job-name: "python-{0}"
-      nox-args: "-s check-docs"
-  javascript:
-    uses: ./.github/workflows/.nox-session.yml
-    with:
-      job-name: "{1}"
-      nox-args: "-t check-javascript"
diff --git a/.gitignore b/.gitignore
index 652e5015b..20c041e11 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,3 @@
-# --- Build Artifacts ---
-src/reactpy/_client
-
 # --- Jupyter ---
 *.ipynb_checkpoints
 *Untitled*.ipynb
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
deleted file mode 100644
index b28e5d978..000000000
--- a/.pre-commit-config.yaml
+++ /dev/null
@@ -1,14 +0,0 @@
-repos:
-  - repo: https://github.com/ambv/black
-    rev: 23.1.0
-    hooks:
-      - id: black
-  - repo: https://github.com/pycqa/isort
-    rev: 5.12.0
-    hooks:
-      - id: isort
-        name: isort
-  - repo: https://github.com/pre-commit/mirrors-prettier
-    rev: v3.0.0-alpha.6
-    hooks:
-      - id: prettier
diff --git a/docs/Dockerfile b/docs/Dockerfile
index 12e133d2c..ee28b3265 100644
--- a/docs/Dockerfile
+++ b/docs/Dockerfile
@@ -8,42 +8,26 @@ RUN curl -sL https://deb.nodesource.com/setup_14.x  | bash -
 RUN apt-get install -yq nodejs build-essential
 RUN npm install -g npm@8.5.0
 
-# Create Python Venv
-# ------------------
-ENV VIRTUAL_ENV=/opt/venv
-RUN python3 -m venv $VIRTUAL_ENV
-ENV PATH="$VIRTUAL_ENV/bin:$PATH"
-RUN pip install --upgrade pip
-
-# Install ReactPy
+# Install Pipx
 # ------------
-COPY requirements ./requirements
-RUN pip install -r requirements/build-docs.txt
+RUN pip install pipx
 
+# Copy Files
+# ----------
+COPY LICENSE ./
 COPY src ./src
-COPY scripts ./scripts
-COPY setup.py ./
-COPY pyproject.toml ./
-COPY MANIFEST.in ./
-COPY README.md ./
-RUN pip install .[all]
-
-# COPY License
-# -----------
-COPY LICENSE /app/
-
-# Build the Docs
-# --------------
-COPY docs/__init__.py ./docs/
-COPY docs/app.py ./docs/
-COPY docs/examples.py ./docs/
-COPY docs/source ./docs/source
+COPY docs ./docs
 COPY branding ./branding
-RUN sphinx-build -v -W -b html docs/source docs/build
+
+# Install and Build Docs
+# ----------------------
+WORKDIR /app/docs
+RUN pipx run poetry install
+RUN pipx run poetry run sphinx-build -v -W -b html source build
 
 # Define Entrypoint
 # -----------------
 ENV PORT 5000
 ENV REACTPY_DEBUG_MODE=1
 ENV REACTPY_CHECK_VDOM_SPEC=0
-CMD python scripts/run_docs.py
+CMD pipx run poetry run python main.py
diff --git a/docs/Makefile b/docs/Makefile
deleted file mode 100644
index 69fe55ecf..000000000
--- a/docs/Makefile
+++ /dev/null
@@ -1,19 +0,0 @@
-# Minimal makefile for Sphinx documentation
-#
-
-# You can set these variables from the command line.
-SPHINXOPTS    =
-SPHINXBUILD   = sphinx-build
-SOURCEDIR     = source
-BUILDDIR      = build
-
-# Put it first so that "make" without argument is like "make help".
-help:
-	@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
-
-.PHONY: help Makefile
-
-# Catch-all target: route all unknown targets to Sphinx using the new
-# "make mode" option.  $(O) is meant as a shortcut for $(SPHINXOPTS).
-%: Makefile
-	@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
\ No newline at end of file
diff --git a/docs/__init__.py b/docs/docs_app/__init__.py
similarity index 100%
rename from docs/__init__.py
rename to docs/docs_app/__init__.py
diff --git a/docs/app.py b/docs/docs_app/app.py
similarity index 77%
rename from docs/app.py
rename to docs/docs_app/app.py
index 0b1ad302a..3fe4669ff 100644
--- a/docs/app.py
+++ b/docs/docs_app/app.py
@@ -3,14 +3,15 @@
 
 from sanic import Sanic, response
 
+from docs_app.examples import get_normalized_example_name, load_examples
 from reactpy import component
 from reactpy.backend.sanic import Options, configure, use_request
 from reactpy.core.types import ComponentConstructor
 
-from .examples import get_normalized_example_name, load_examples
+THIS_DIR = Path(__file__).parent
+DOCS_DIR = THIS_DIR.parent
+DOCS_BUILD_DIR = DOCS_DIR / "build"
 
-
-HERE = Path(__file__).parent
 REACTPY_MODEL_SERVER_URL_PREFIX = "/_reactpy"
 
 logger = getLogger(__name__)
@@ -40,13 +41,13 @@ def reload_examples():
 _EXAMPLES: dict[str, ComponentConstructor] = {}
 
 
-def make_app():
-    app = Sanic("docs_app")
+def make_app(name: str):
+    app = Sanic(name)
 
-    app.static("/docs", str(HERE / "build"))
+    app.static("/docs", str(DOCS_BUILD_DIR))
 
     @app.route("/")
-    async def forward_to_index(request):
+    async def forward_to_index(_):
         return response.redirect("/docs/index.html")
 
     configure(
diff --git a/scripts/live_docs.py b/docs/docs_app/dev.py
similarity index 88%
rename from scripts/live_docs.py
rename to docs/docs_app/dev.py
index 3f7d8b207..5d661924d 100644
--- a/scripts/live_docs.py
+++ b/docs/docs_app/dev.py
@@ -13,22 +13,19 @@
     get_parser,
 )
 
-from docs.app import make_app, reload_examples
+from docs_app.app import make_app, reload_examples
 from reactpy.backend.sanic import serve_development_app
 from reactpy.testing import clear_reactpy_web_modules_dir
 
-
 # these environment variable are used in custom Sphinx extensions
 os.environ["REACTPY_DOC_EXAMPLE_SERVER_HOST"] = "127.0.0.1:5555"
 os.environ["REACTPY_DOC_STATIC_SERVER_HOST"] = ""
 
-_running_reactpy_servers = []
-
 
 def wrap_builder(old_builder):
     # This is the bit that we're injecting to get the example components to reload too
 
-    app = make_app()
+    app = make_app("docs_dev_app")
 
     thread_started = threading.Event()
 
@@ -87,8 +84,8 @@ def main():
     ignore_handler = _get_ignore_handler(args)
     server.watch(srcdir, builder, ignore=ignore_handler)
     for dirpath in args.additional_watched_dirs:
-        dirpath = os.path.realpath(dirpath)
-        server.watch(dirpath, builder, ignore=ignore_handler)
+        real_dirpath = os.path.realpath(dirpath)
+        server.watch(real_dirpath, builder, ignore=ignore_handler)
     server.watch(outdir, ignore=ignore_handler)
 
     if not args.no_initial_build:
@@ -100,12 +97,8 @@ def main():
 
         def opener():
             time.sleep(args.delay)
-            webbrowser.open("http://%s:%s/index.html" % (args.host, args.port))
+            webbrowser.open(f"http://{args.host}:{args.port}/index.html")
 
         threading.Thread(target=opener, daemon=True).start()
 
     server.serve(port=portn, host=args.host, root=outdir)
-
-
-if __name__ == "__main__":
-    main()
diff --git a/docs/examples.py b/docs/docs_app/examples.py
similarity index 97%
rename from docs/examples.py
rename to docs/docs_app/examples.py
index 142d49429..a71a0b111 100644
--- a/docs/examples.py
+++ b/docs/docs_app/examples.py
@@ -1,16 +1,16 @@
 from __future__ import annotations
 
+from collections.abc import Iterator
 from io import StringIO
 from pathlib import Path
 from traceback import format_exc
-from typing import Callable, Iterator
+from typing import Callable
 
 import reactpy
 from reactpy.types import ComponentType
 
-
 HERE = Path(__file__)
-SOURCE_DIR = HERE.parent / "source"
+SOURCE_DIR = HERE.parent.parent / "source"
 CONF_FILE = SOURCE_DIR / "conf.py"
 RUN_ReactPy = reactpy.run
 
@@ -148,7 +148,6 @@ def __init__(self, max_lines: int = 10):
 
     def set_callback(self, function: Callable[[str], None]) -> None:
         self._callback = function
-        return None
 
     def getvalue(self) -> str:
         return "".join(self._lines)
diff --git a/docs/docs_app/prod.py b/docs/docs_app/prod.py
new file mode 100644
index 000000000..0acf12432
--- /dev/null
+++ b/docs/docs_app/prod.py
@@ -0,0 +1,14 @@
+import os
+
+from docs_app.app import make_app
+
+app = make_app("docs_prod_app")
+
+
+def main() -> None:
+    app.run(
+        host="0.0.0.0",  # noqa: S104
+        port=int(os.environ.get("PORT", 5000)),
+        workers=int(os.environ.get("WEB_CONCURRENCY", 1)),
+        debug=bool(int(os.environ.get("DEBUG", "0"))),
+    )
diff --git a/docs/main.py b/docs/main.py
new file mode 100644
index 000000000..e3181f393
--- /dev/null
+++ b/docs/main.py
@@ -0,0 +1,9 @@
+import sys
+
+from docs_app import dev, prod
+
+if __name__ == "__main__":
+    if len(sys.argv) == 1:
+        prod.main()
+    else:
+        dev.main()
diff --git a/docs/make.bat b/docs/make.bat
deleted file mode 100644
index 543c6b13b..000000000
--- a/docs/make.bat
+++ /dev/null
@@ -1,35 +0,0 @@
-@ECHO OFF
-
-pushd %~dp0
-
-REM Command file for Sphinx documentation
-
-if "%SPHINXBUILD%" == "" (
-	set SPHINXBUILD=sphinx-build
-)
-set SOURCEDIR=source
-set BUILDDIR=build
-
-if "%1" == "" goto help
-
-%SPHINXBUILD% >NUL 2>NUL
-if errorlevel 9009 (
-	echo.
-	echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
-	echo.installed, then set the SPHINXBUILD environment variable to point
-	echo.to the full path of the 'sphinx-build' executable. Alternatively you
-	echo.may add the Sphinx directory to PATH.
-	echo.
-	echo.If you don't have Sphinx installed, grab it from
-	echo.http://sphinx-doc.org/
-	exit /b 1
-)
-
-%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
-goto end
-
-:help
-%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
-
-:end
-popd
diff --git a/docs/poetry.lock b/docs/poetry.lock
new file mode 100644
index 000000000..8e1daef24
--- /dev/null
+++ b/docs/poetry.lock
@@ -0,0 +1,2269 @@
+# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand.
+
+[[package]]
+name = "aiofiles"
+version = "23.1.0"
+description = "File support for asyncio."
+optional = false
+python-versions = ">=3.7,<4.0"
+files = [
+    {file = "aiofiles-23.1.0-py3-none-any.whl", hash = "sha256:9312414ae06472eb6f1d163f555e466a23aed1c8f60c30cccf7121dba2e53eb2"},
+    {file = "aiofiles-23.1.0.tar.gz", hash = "sha256:edd247df9a19e0db16534d4baaf536d6609a43e1de5401d7a4c1c148753a1635"},
+]
+
+[[package]]
+name = "alabaster"
+version = "0.7.13"
+description = "A configurable sidebar-enabled Sphinx theme"
+optional = false
+python-versions = ">=3.6"
+files = [
+    {file = "alabaster-0.7.13-py3-none-any.whl", hash = "sha256:1ee19aca801bbabb5ba3f5f258e4422dfa86f82f3e9cefb0859b283cdd7f62a3"},
+    {file = "alabaster-0.7.13.tar.gz", hash = "sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2"},
+]
+
+[[package]]
+name = "anyio"
+version = "3.7.0"
+description = "High level compatibility layer for multiple asynchronous event loop implementations"
+optional = false
+python-versions = ">=3.7"
+files = [
+    {file = "anyio-3.7.0-py3-none-any.whl", hash = "sha256:eddca883c4175f14df8aedce21054bfca3adb70ffe76a9f607aef9d7fa2ea7f0"},
+    {file = "anyio-3.7.0.tar.gz", hash = "sha256:275d9973793619a5374e1c89a4f4ad3f4b0a5510a2b5b939444bee8f4c4d37ce"},
+]
+
+[package.dependencies]
+exceptiongroup = {version = "*", markers = "python_version < \"3.11\""}
+idna = ">=2.8"
+sniffio = ">=1.1"
+
+[package.extras]
+doc = ["Sphinx (>=6.1.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme", "sphinxcontrib-jquery"]
+test = ["anyio[trio]", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"]
+trio = ["trio (<0.22)"]
+
+[[package]]
+name = "asgiref"
+version = "3.7.2"
+description = "ASGI specs, helper code, and adapters"
+optional = false
+python-versions = ">=3.7"
+files = [
+    {file = "asgiref-3.7.2-py3-none-any.whl", hash = "sha256:89b2ef2247e3b562a16eef663bc0e2e703ec6468e2fa8a5cd61cd449786d4f6e"},
+    {file = "asgiref-3.7.2.tar.gz", hash = "sha256:9e0ce3aa93a819ba5b45120216b23878cf6e8525eb3848653452b4192b92afed"},
+]
+
+[package.dependencies]
+typing-extensions = {version = ">=4", markers = "python_version < \"3.11\""}
+
+[package.extras]
+tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"]
+
+[[package]]
+name = "babel"
+version = "2.12.1"
+description = "Internationalization utilities"
+optional = false
+python-versions = ">=3.7"
+files = [
+    {file = "Babel-2.12.1-py3-none-any.whl", hash = "sha256:b4246fb7677d3b98f501a39d43396d3cafdc8eadb045f4a31be01863f655c610"},
+    {file = "Babel-2.12.1.tar.gz", hash = "sha256:cc2d99999cd01d44420ae725a21c9e3711b3aadc7976d6147f622d8581963455"},
+]
+
+[[package]]
+name = "beautifulsoup4"
+version = "4.12.2"
+description = "Screen-scraping library"
+optional = false
+python-versions = ">=3.6.0"
+files = [
+    {file = "beautifulsoup4-4.12.2-py3-none-any.whl", hash = "sha256:bd2520ca0d9d7d12694a53d44ac482d181b4ec1888909b035a3dbf40d0f57d4a"},
+    {file = "beautifulsoup4-4.12.2.tar.gz", hash = "sha256:492bbc69dca35d12daac71c4db1bfff0c876c00ef4a2ffacce226d4638eb72da"},
+]
+
+[package.dependencies]
+soupsieve = ">1.2"
+
+[package.extras]
+html5lib = ["html5lib"]
+lxml = ["lxml"]
+
+[[package]]
+name = "certifi"
+version = "2023.5.7"
+description = "Python package for providing Mozilla's CA Bundle."
+optional = false
+python-versions = ">=3.6"
+files = [
+    {file = "certifi-2023.5.7-py3-none-any.whl", hash = "sha256:c6c2e98f5c7869efca1f8916fed228dd91539f9f1b444c314c06eef02980c716"},
+    {file = "certifi-2023.5.7.tar.gz", hash = "sha256:0f0d56dc5a6ad56fd4ba36484d6cc34451e1c6548c61daad8c320169f91eddc7"},
+]
+
+[[package]]
+name = "charset-normalizer"
+version = "3.1.0"
+description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
+optional = false
+python-versions = ">=3.7.0"
+files = [
+    {file = "charset-normalizer-3.1.0.tar.gz", hash = "sha256:34e0a2f9c370eb95597aae63bf85eb5e96826d81e3dcf88b8886012906f509b5"},
+    {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e0ac8959c929593fee38da1c2b64ee9778733cdf03c482c9ff1d508b6b593b2b"},
+    {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d7fc3fca01da18fbabe4625d64bb612b533533ed10045a2ac3dd194bfa656b60"},
+    {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:04eefcee095f58eaabe6dc3cc2262f3bcd776d2c67005880894f447b3f2cb9c1"},
+    {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20064ead0717cf9a73a6d1e779b23d149b53daf971169289ed2ed43a71e8d3b0"},
+    {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1435ae15108b1cb6fffbcea2af3d468683b7afed0169ad718451f8db5d1aff6f"},
+    {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c84132a54c750fda57729d1e2599bb598f5fa0344085dbde5003ba429a4798c0"},
+    {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75f2568b4189dda1c567339b48cba4ac7384accb9c2a7ed655cd86b04055c795"},
+    {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11d3bcb7be35e7b1bba2c23beedac81ee893ac9871d0ba79effc7fc01167db6c"},
+    {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:891cf9b48776b5c61c700b55a598621fdb7b1e301a550365571e9624f270c203"},
+    {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:5f008525e02908b20e04707a4f704cd286d94718f48bb33edddc7d7b584dddc1"},
+    {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:b06f0d3bf045158d2fb8837c5785fe9ff9b8c93358be64461a1089f5da983137"},
+    {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:49919f8400b5e49e961f320c735388ee686a62327e773fa5b3ce6721f7e785ce"},
+    {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:22908891a380d50738e1f978667536f6c6b526a2064156203d418f4856d6e86a"},
+    {file = "charset_normalizer-3.1.0-cp310-cp310-win32.whl", hash = "sha256:12d1a39aa6b8c6f6248bb54550efcc1c38ce0d8096a146638fd4738e42284448"},
+    {file = "charset_normalizer-3.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:65ed923f84a6844de5fd29726b888e58c62820e0769b76565480e1fdc3d062f8"},
+    {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9a3267620866c9d17b959a84dd0bd2d45719b817245e49371ead79ed4f710d19"},
+    {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6734e606355834f13445b6adc38b53c0fd45f1a56a9ba06c2058f86893ae8017"},
+    {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f8303414c7b03f794347ad062c0516cee0e15f7a612abd0ce1e25caf6ceb47df"},
+    {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aaf53a6cebad0eae578f062c7d462155eada9c172bd8c4d250b8c1d8eb7f916a"},
+    {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3dc5b6a8ecfdc5748a7e429782598e4f17ef378e3e272eeb1340ea57c9109f41"},
+    {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e1b25e3ad6c909f398df8921780d6a3d120d8c09466720226fc621605b6f92b1"},
+    {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ca564606d2caafb0abe6d1b5311c2649e8071eb241b2d64e75a0d0065107e62"},
+    {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b82fab78e0b1329e183a65260581de4375f619167478dddab510c6c6fb04d9b6"},
+    {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bd7163182133c0c7701b25e604cf1611c0d87712e56e88e7ee5d72deab3e76b5"},
+    {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:11d117e6c63e8f495412d37e7dc2e2fff09c34b2d09dbe2bee3c6229577818be"},
+    {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:cf6511efa4801b9b38dc5546d7547d5b5c6ef4b081c60b23e4d941d0eba9cbeb"},
+    {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:abc1185d79f47c0a7aaf7e2412a0eb2c03b724581139193d2d82b3ad8cbb00ac"},
+    {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cb7b2ab0188829593b9de646545175547a70d9a6e2b63bf2cd87a0a391599324"},
+    {file = "charset_normalizer-3.1.0-cp311-cp311-win32.whl", hash = "sha256:c36bcbc0d5174a80d6cccf43a0ecaca44e81d25be4b7f90f0ed7bcfbb5a00909"},
+    {file = "charset_normalizer-3.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:cca4def576f47a09a943666b8f829606bcb17e2bc2d5911a46c8f8da45f56755"},
+    {file = "charset_normalizer-3.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0c95f12b74681e9ae127728f7e5409cbbef9cd914d5896ef238cc779b8152373"},
+    {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fca62a8301b605b954ad2e9c3666f9d97f63872aa4efcae5492baca2056b74ab"},
+    {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac0aa6cd53ab9a31d397f8303f92c42f534693528fafbdb997c82bae6e477ad9"},
+    {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3af8e0f07399d3176b179f2e2634c3ce9c1301379a6b8c9c9aeecd481da494f"},
+    {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a5fc78f9e3f501a1614a98f7c54d3969f3ad9bba8ba3d9b438c3bc5d047dd28"},
+    {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:628c985afb2c7d27a4800bfb609e03985aaecb42f955049957814e0491d4006d"},
+    {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:74db0052d985cf37fa111828d0dd230776ac99c740e1a758ad99094be4f1803d"},
+    {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:1e8fcdd8f672a1c4fc8d0bd3a2b576b152d2a349782d1eb0f6b8e52e9954731d"},
+    {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:04afa6387e2b282cf78ff3dbce20f0cc071c12dc8f685bd40960cc68644cfea6"},
+    {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:dd5653e67b149503c68c4018bf07e42eeed6b4e956b24c00ccdf93ac79cdff84"},
+    {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d2686f91611f9e17f4548dbf050e75b079bbc2a82be565832bc8ea9047b61c8c"},
+    {file = "charset_normalizer-3.1.0-cp37-cp37m-win32.whl", hash = "sha256:4155b51ae05ed47199dc5b2a4e62abccb274cee6b01da5b895099b61b1982974"},
+    {file = "charset_normalizer-3.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:322102cdf1ab682ecc7d9b1c5eed4ec59657a65e1c146a0da342b78f4112db23"},
+    {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e633940f28c1e913615fd624fcdd72fdba807bf53ea6925d6a588e84e1151531"},
+    {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3a06f32c9634a8705f4ca9946d667609f52cf130d5548881401f1eb2c39b1e2c"},
+    {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7381c66e0561c5757ffe616af869b916c8b4e42b367ab29fedc98481d1e74e14"},
+    {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3573d376454d956553c356df45bb824262c397c6e26ce43e8203c4c540ee0acb"},
+    {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e89df2958e5159b811af9ff0f92614dabf4ff617c03a4c1c6ff53bf1c399e0e1"},
+    {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:78cacd03e79d009d95635e7d6ff12c21eb89b894c354bd2b2ed0b4763373693b"},
+    {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de5695a6f1d8340b12a5d6d4484290ee74d61e467c39ff03b39e30df62cf83a0"},
+    {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c60b9c202d00052183c9be85e5eaf18a4ada0a47d188a83c8f5c5b23252f649"},
+    {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f645caaf0008bacf349875a974220f1f1da349c5dbe7c4ec93048cdc785a3326"},
+    {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ea9f9c6034ea2d93d9147818f17c2a0860d41b71c38b9ce4d55f21b6f9165a11"},
+    {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:80d1543d58bd3d6c271b66abf454d437a438dff01c3e62fdbcd68f2a11310d4b"},
+    {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:73dc03a6a7e30b7edc5b01b601e53e7fc924b04e1835e8e407c12c037e81adbd"},
+    {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6f5c2e7bc8a4bf7c426599765b1bd33217ec84023033672c1e9a8b35eaeaaaf8"},
+    {file = "charset_normalizer-3.1.0-cp38-cp38-win32.whl", hash = "sha256:12a2b561af122e3d94cdb97fe6fb2bb2b82cef0cdca131646fdb940a1eda04f0"},
+    {file = "charset_normalizer-3.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:3160a0fd9754aab7d47f95a6b63ab355388d890163eb03b2d2b87ab0a30cfa59"},
+    {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:38e812a197bf8e71a59fe55b757a84c1f946d0ac114acafaafaf21667a7e169e"},
+    {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6baf0baf0d5d265fa7944feb9f7451cc316bfe30e8df1a61b1bb08577c554f31"},
+    {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8f25e17ab3039b05f762b0a55ae0b3632b2e073d9c8fc88e89aca31a6198e88f"},
+    {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3747443b6a904001473370d7810aa19c3a180ccd52a7157aacc264a5ac79265e"},
+    {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b116502087ce8a6b7a5f1814568ccbd0e9f6cfd99948aa59b0e241dc57cf739f"},
+    {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d16fd5252f883eb074ca55cb622bc0bee49b979ae4e8639fff6ca3ff44f9f854"},
+    {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21fa558996782fc226b529fdd2ed7866c2c6ec91cee82735c98a197fae39f706"},
+    {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f6c7a8a57e9405cad7485f4c9d3172ae486cfef1344b5ddd8e5239582d7355e"},
+    {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ac3775e3311661d4adace3697a52ac0bab17edd166087d493b52d4f4f553f9f0"},
+    {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:10c93628d7497c81686e8e5e557aafa78f230cd9e77dd0c40032ef90c18f2230"},
+    {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:6f4f4668e1831850ebcc2fd0b1cd11721947b6dc7c00bf1c6bd3c929ae14f2c7"},
+    {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:0be65ccf618c1e7ac9b849c315cc2e8a8751d9cfdaa43027d4f6624bd587ab7e"},
+    {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:53d0a3fa5f8af98a1e261de6a3943ca631c526635eb5817a87a59d9a57ebf48f"},
+    {file = "charset_normalizer-3.1.0-cp39-cp39-win32.whl", hash = "sha256:a04f86f41a8916fe45ac5024ec477f41f886b3c435da2d4e3d2709b22ab02af1"},
+    {file = "charset_normalizer-3.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:830d2948a5ec37c386d3170c483063798d7879037492540f10a475e3fd6f244b"},
+    {file = "charset_normalizer-3.1.0-py3-none-any.whl", hash = "sha256:3d9098b479e78c85080c98e1e35ff40b4a31d8953102bb0fd7d1b6f8a2111a3d"},
+]
+
+[[package]]
+name = "click"
+version = "8.1.3"
+description = "Composable command line interface toolkit"
+optional = false
+python-versions = ">=3.7"
+files = [
+    {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"},
+    {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"},
+]
+
+[package.dependencies]
+colorama = {version = "*", markers = "platform_system == \"Windows\""}
+
+[[package]]
+name = "colorama"
+version = "0.4.6"
+description = "Cross-platform colored terminal text."
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
+files = [
+    {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
+    {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
+]
+
+[[package]]
+name = "colorlog"
+version = "6.7.0"
+description = "Add colours to the output of Python's logging module."
+optional = false
+python-versions = ">=3.6"
+files = [
+    {file = "colorlog-6.7.0-py2.py3-none-any.whl", hash = "sha256:0d33ca236784a1ba3ff9c532d4964126d8a2c44f1f0cb1d2b0728196f512f662"},
+    {file = "colorlog-6.7.0.tar.gz", hash = "sha256:bd94bd21c1e13fac7bd3153f4bc3a7dc0eb0974b8bc2fdf1a989e474f6e582e5"},
+]
+
+[package.dependencies]
+colorama = {version = "*", markers = "sys_platform == \"win32\""}
+
+[package.extras]
+development = ["black", "flake8", "mypy", "pytest", "types-colorama"]
+
+[[package]]
+name = "contourpy"
+version = "1.0.7"
+description = "Python library for calculating contours of 2D quadrilateral grids"
+optional = false
+python-versions = ">=3.8"
+files = [
+    {file = "contourpy-1.0.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:95c3acddf921944f241b6773b767f1cbce71d03307270e2d769fd584d5d1092d"},
+    {file = "contourpy-1.0.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fc1464c97579da9f3ab16763c32e5c5d5bb5fa1ec7ce509a4ca6108b61b84fab"},
+    {file = "contourpy-1.0.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8acf74b5d383414401926c1598ed77825cd530ac7b463ebc2e4f46638f56cce6"},
+    {file = "contourpy-1.0.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c71fdd8f1c0f84ffd58fca37d00ca4ebaa9e502fb49825484da075ac0b0b803"},
+    {file = "contourpy-1.0.7-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f99e9486bf1bb979d95d5cffed40689cb595abb2b841f2991fc894b3452290e8"},
+    {file = "contourpy-1.0.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87f4d8941a9564cda3f7fa6a6cd9b32ec575830780677932abdec7bcb61717b0"},
+    {file = "contourpy-1.0.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9e20e5a1908e18aaa60d9077a6d8753090e3f85ca25da6e25d30dc0a9e84c2c6"},
+    {file = "contourpy-1.0.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a877ada905f7d69b2a31796c4b66e31a8068b37aa9b78832d41c82fc3e056ddd"},
+    {file = "contourpy-1.0.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6381fa66866b0ea35e15d197fc06ac3840a9b2643a6475c8fff267db8b9f1e69"},
+    {file = "contourpy-1.0.7-cp310-cp310-win32.whl", hash = "sha256:3c184ad2433635f216645fdf0493011a4667e8d46b34082f5a3de702b6ec42e3"},
+    {file = "contourpy-1.0.7-cp310-cp310-win_amd64.whl", hash = "sha256:3caea6365b13119626ee996711ab63e0c9d7496f65641f4459c60a009a1f3e80"},
+    {file = "contourpy-1.0.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ed33433fc3820263a6368e532f19ddb4c5990855e4886088ad84fd7c4e561c71"},
+    {file = "contourpy-1.0.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:38e2e577f0f092b8e6774459317c05a69935a1755ecfb621c0a98f0e3c09c9a5"},
+    {file = "contourpy-1.0.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ae90d5a8590e5310c32a7630b4b8618cef7563cebf649011da80874d0aa8f414"},
+    {file = "contourpy-1.0.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:130230b7e49825c98edf0b428b7aa1125503d91732735ef897786fe5452b1ec2"},
+    {file = "contourpy-1.0.7-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58569c491e7f7e874f11519ef46737cea1d6eda1b514e4eb5ac7dab6aa864d02"},
+    {file = "contourpy-1.0.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54d43960d809c4c12508a60b66cb936e7ed57d51fb5e30b513934a4a23874fae"},
+    {file = "contourpy-1.0.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:152fd8f730c31fd67fe0ffebe1df38ab6a669403da93df218801a893645c6ccc"},
+    {file = "contourpy-1.0.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:9056c5310eb1daa33fc234ef39ebfb8c8e2533f088bbf0bc7350f70a29bde1ac"},
+    {file = "contourpy-1.0.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a9d7587d2fdc820cc9177139b56795c39fb8560f540bba9ceea215f1f66e1566"},
+    {file = "contourpy-1.0.7-cp311-cp311-win32.whl", hash = "sha256:4ee3ee247f795a69e53cd91d927146fb16c4e803c7ac86c84104940c7d2cabf0"},
+    {file = "contourpy-1.0.7-cp311-cp311-win_amd64.whl", hash = "sha256:5caeacc68642e5f19d707471890f037a13007feba8427eb7f2a60811a1fc1350"},
+    {file = "contourpy-1.0.7-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fd7dc0e6812b799a34f6d12fcb1000539098c249c8da54f3566c6a6461d0dbad"},
+    {file = "contourpy-1.0.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0f9d350b639db6c2c233d92c7f213d94d2e444d8e8fc5ca44c9706cf72193772"},
+    {file = "contourpy-1.0.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e96a08b62bb8de960d3a6afbc5ed8421bf1a2d9c85cc4ea73f4bc81b4910500f"},
+    {file = "contourpy-1.0.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:031154ed61f7328ad7f97662e48660a150ef84ee1bc8876b6472af88bf5a9b98"},
+    {file = "contourpy-1.0.7-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e9ebb4425fc1b658e13bace354c48a933b842d53c458f02c86f371cecbedecc"},
+    {file = "contourpy-1.0.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efb8f6d08ca7998cf59eaf50c9d60717f29a1a0a09caa46460d33b2924839dbd"},
+    {file = "contourpy-1.0.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6c180d89a28787e4b73b07e9b0e2dac7741261dbdca95f2b489c4f8f887dd810"},
+    {file = "contourpy-1.0.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b8d587cc39057d0afd4166083d289bdeff221ac6d3ee5046aef2d480dc4b503c"},
+    {file = "contourpy-1.0.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:769eef00437edf115e24d87f8926955f00f7704bede656ce605097584f9966dc"},
+    {file = "contourpy-1.0.7-cp38-cp38-win32.whl", hash = "sha256:62398c80ef57589bdbe1eb8537127321c1abcfdf8c5f14f479dbbe27d0322e66"},
+    {file = "contourpy-1.0.7-cp38-cp38-win_amd64.whl", hash = "sha256:57119b0116e3f408acbdccf9eb6ef19d7fe7baf0d1e9aaa5381489bc1aa56556"},
+    {file = "contourpy-1.0.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:30676ca45084ee61e9c3da589042c24a57592e375d4b138bd84d8709893a1ba4"},
+    {file = "contourpy-1.0.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3e927b3868bd1e12acee7cc8f3747d815b4ab3e445a28d2e5373a7f4a6e76ba1"},
+    {file = "contourpy-1.0.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:366a0cf0fc079af5204801786ad7a1c007714ee3909e364dbac1729f5b0849e5"},
+    {file = "contourpy-1.0.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89ba9bb365446a22411f0673abf6ee1fea3b2cf47b37533b970904880ceb72f3"},
+    {file = "contourpy-1.0.7-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:71b0bf0c30d432278793d2141362ac853859e87de0a7dee24a1cea35231f0d50"},
+    {file = "contourpy-1.0.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7281244c99fd7c6f27c1c6bfafba878517b0b62925a09b586d88ce750a016d2"},
+    {file = "contourpy-1.0.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b6d0f9e1d39dbfb3977f9dd79f156c86eb03e57a7face96f199e02b18e58d32a"},
+    {file = "contourpy-1.0.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7f6979d20ee5693a1057ab53e043adffa1e7418d734c1532e2d9e915b08d8ec2"},
+    {file = "contourpy-1.0.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5dd34c1ae752515318224cba7fc62b53130c45ac6a1040c8b7c1a223c46e8967"},
+    {file = "contourpy-1.0.7-cp39-cp39-win32.whl", hash = "sha256:c5210e5d5117e9aec8c47d9156d1d3835570dd909a899171b9535cb4a3f32693"},
+    {file = "contourpy-1.0.7-cp39-cp39-win_amd64.whl", hash = "sha256:60835badb5ed5f4e194a6f21c09283dd6e007664a86101431bf870d9e86266c4"},
+    {file = "contourpy-1.0.7-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ce41676b3d0dd16dbcfabcc1dc46090aaf4688fd6e819ef343dbda5a57ef0161"},
+    {file = "contourpy-1.0.7-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a011cf354107b47c58ea932d13b04d93c6d1d69b8b6dce885e642531f847566"},
+    {file = "contourpy-1.0.7-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:31a55dccc8426e71817e3fe09b37d6d48ae40aae4ecbc8c7ad59d6893569c436"},
+    {file = "contourpy-1.0.7-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69f8ff4db108815addd900a74df665e135dbbd6547a8a69333a68e1f6e368ac2"},
+    {file = "contourpy-1.0.7-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:efe99298ba37e37787f6a2ea868265465410822f7bea163edcc1bd3903354ea9"},
+    {file = "contourpy-1.0.7-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a1e97b86f73715e8670ef45292d7cc033548266f07d54e2183ecb3c87598888f"},
+    {file = "contourpy-1.0.7-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc331c13902d0f50845099434cd936d49d7a2ca76cb654b39691974cb1e4812d"},
+    {file = "contourpy-1.0.7-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:24847601071f740837aefb730e01bd169fbcaa610209779a78db7ebb6e6a7051"},
+    {file = "contourpy-1.0.7-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abf298af1e7ad44eeb93501e40eb5a67abbf93b5d90e468d01fc0c4451971afa"},
+    {file = "contourpy-1.0.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:64757f6460fc55d7e16ed4f1de193f362104285c667c112b50a804d482777edd"},
+    {file = "contourpy-1.0.7.tar.gz", hash = "sha256:d8165a088d31798b59e91117d1f5fc3df8168d8b48c4acc10fc0df0d0bdbcc5e"},
+]
+
+[package.dependencies]
+numpy = ">=1.16"
+
+[package.extras]
+bokeh = ["bokeh", "chromedriver", "selenium"]
+docs = ["furo", "sphinx-copybutton"]
+mypy = ["contourpy[bokeh]", "docutils-stubs", "mypy (==0.991)", "types-Pillow"]
+test = ["Pillow", "matplotlib", "pytest"]
+test-no-images = ["pytest"]
+
+[[package]]
+name = "cycler"
+version = "0.11.0"
+description = "Composable style cycles"
+optional = false
+python-versions = ">=3.6"
+files = [
+    {file = "cycler-0.11.0-py3-none-any.whl", hash = "sha256:3a27e95f763a428a739d2add979fa7494c912a32c17c4c38c4d5f082cad165a3"},
+    {file = "cycler-0.11.0.tar.gz", hash = "sha256:9c87405839a19696e837b3b818fed3f5f69f16f1eec1a1ad77e043dcea9c772f"},
+]
+
+[[package]]
+name = "docutils"
+version = "0.17.1"
+description = "Docutils -- Python Documentation Utilities"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+files = [
+    {file = "docutils-0.17.1-py2.py3-none-any.whl", hash = "sha256:cf316c8370a737a022b72b56874f6602acf974a37a9fba42ec2876387549fc61"},
+    {file = "docutils-0.17.1.tar.gz", hash = "sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125"},
+]
+
+[[package]]
+name = "exceptiongroup"
+version = "1.1.1"
+description = "Backport of PEP 654 (exception groups)"
+optional = false
+python-versions = ">=3.7"
+files = [
+    {file = "exceptiongroup-1.1.1-py3-none-any.whl", hash = "sha256:232c37c63e4f682982c8b6459f33a8981039e5fb8756b2074364e5055c498c9e"},
+    {file = "exceptiongroup-1.1.1.tar.gz", hash = "sha256:d484c3090ba2889ae2928419117447a14daf3c1231d5e30d0aae34f354f01785"},
+]
+
+[package.extras]
+test = ["pytest (>=6)"]
+
+[[package]]
+name = "fastapi"
+version = "0.96.0"
+description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
+optional = false
+python-versions = ">=3.7"
+files = [
+    {file = "fastapi-0.96.0-py3-none-any.whl", hash = "sha256:b8e11fe81e81eab4e1504209917338e0b80f783878a42c2b99467e5e1019a1e9"},
+    {file = "fastapi-0.96.0.tar.gz", hash = "sha256:71232d47c2787446991c81c41c249f8a16238d52d779c0e6b43927d3773dbe3c"},
+]
+
+[package.dependencies]
+pydantic = ">=1.6.2,<1.7 || >1.7,<1.7.1 || >1.7.1,<1.7.2 || >1.7.2,<1.7.3 || >1.7.3,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0"
+starlette = ">=0.27.0,<0.28.0"
+
+[package.extras]
+all = ["email-validator (>=1.1.1)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "python-multipart (>=0.0.5)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"]
+dev = ["pre-commit (>=2.17.0,<3.0.0)", "ruff (==0.0.138)", "uvicorn[standard] (>=0.12.0,<0.21.0)"]
+doc = ["mdx-include (>=1.4.1,<2.0.0)", "mkdocs (>=1.1.2,<2.0.0)", "mkdocs-markdownextradata-plugin (>=0.1.7,<0.3.0)", "mkdocs-material (>=8.1.4,<9.0.0)", "pyyaml (>=5.3.1,<7.0.0)", "typer-cli (>=0.0.13,<0.0.14)", "typer[all] (>=0.6.1,<0.8.0)"]
+test = ["anyio[trio] (>=3.2.1,<4.0.0)", "black (==23.1.0)", "coverage[toml] (>=6.5.0,<8.0)", "databases[sqlite] (>=0.3.2,<0.7.0)", "email-validator (>=1.1.1,<2.0.0)", "flask (>=1.1.2,<3.0.0)", "httpx (>=0.23.0,<0.24.0)", "isort (>=5.0.6,<6.0.0)", "mypy (==0.982)", "orjson (>=3.2.1,<4.0.0)", "passlib[bcrypt] (>=1.7.2,<2.0.0)", "peewee (>=3.13.3,<4.0.0)", "pytest (>=7.1.3,<8.0.0)", "python-jose[cryptography] (>=3.3.0,<4.0.0)", "python-multipart (>=0.0.5,<0.0.7)", "pyyaml (>=5.3.1,<7.0.0)", "ruff (==0.0.138)", "sqlalchemy (>=1.3.18,<1.4.43)", "types-orjson (==3.6.2)", "types-ujson (==5.7.0.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0,<6.0.0)"]
+
+[[package]]
+name = "fastjsonschema"
+version = "2.17.1"
+description = "Fastest Python implementation of JSON schema"
+optional = false
+python-versions = "*"
+files = [
+    {file = "fastjsonschema-2.17.1-py3-none-any.whl", hash = "sha256:4b90b252628ca695280924d863fe37234eebadc29c5360d322571233dc9746e0"},
+    {file = "fastjsonschema-2.17.1.tar.gz", hash = "sha256:f4eeb8a77cef54861dbf7424ac8ce71306f12cbb086c45131bcba2c6a4f726e3"},
+]
+
+[package.extras]
+devel = ["colorama", "json-spec", "jsonschema", "pylint", "pytest", "pytest-benchmark", "pytest-cache", "validictory"]
+
+[[package]]
+name = "flask"
+version = "2.1.3"
+description = "A simple framework for building complex web applications."
+optional = false
+python-versions = ">=3.7"
+files = [
+    {file = "Flask-2.1.3-py3-none-any.whl", hash = "sha256:9013281a7402ad527f8fd56375164f3aa021ecfaff89bfe3825346c24f87e04c"},
+    {file = "Flask-2.1.3.tar.gz", hash = "sha256:15972e5017df0575c3d6c090ba168b6db90259e620ac8d7ea813a396bad5b6cb"},
+]
+
+[package.dependencies]
+click = ">=8.0"
+importlib-metadata = {version = ">=3.6.0", markers = "python_version < \"3.10\""}
+itsdangerous = ">=2.0"
+Jinja2 = ">=3.0"
+Werkzeug = ">=2.0"
+
+[package.extras]
+async = ["asgiref (>=3.2)"]
+dotenv = ["python-dotenv"]
+
+[[package]]
+name = "flask-cors"
+version = "3.0.10"
+description = "A Flask extension adding a decorator for CORS support"
+optional = false
+python-versions = "*"
+files = [
+    {file = "Flask-Cors-3.0.10.tar.gz", hash = "sha256:b60839393f3b84a0f3746f6cdca56c1ad7426aa738b70d6c61375857823181de"},
+    {file = "Flask_Cors-3.0.10-py2.py3-none-any.whl", hash = "sha256:74efc975af1194fc7891ff5cd85b0f7478be4f7f59fe158102e91abb72bb4438"},
+]
+
+[package.dependencies]
+Flask = ">=0.9"
+Six = "*"
+
+[[package]]
+name = "flask-sock"
+version = "0.6.0"
+description = "WebSocket support for Flask"
+optional = false
+python-versions = ">=3.6"
+files = [
+    {file = "flask-sock-0.6.0.tar.gz", hash = "sha256:435cf81bb497ac7622cd1dda554fbfa3e369e629daea0a1d21b73a24f1bd6229"},
+    {file = "flask_sock-0.6.0-py3-none-any.whl", hash = "sha256:593fffb186928080a5b5b03d717efc56dac2d5ed690ce6bfff333b3597a2f518"},
+]
+
+[package.dependencies]
+flask = ">=2"
+simple-websocket = ">=0.5.1"
+
+[[package]]
+name = "fonttools"
+version = "4.39.4"
+description = "Tools to manipulate font files"
+optional = false
+python-versions = ">=3.8"
+files = [
+    {file = "fonttools-4.39.4-py3-none-any.whl", hash = "sha256:106caf6167c4597556b31a8d9175a3fdc0356fdcd70ab19973c3b0d4c893c461"},
+    {file = "fonttools-4.39.4.zip", hash = "sha256:dba8d7cdb8e2bac1b3da28c5ed5960de09e59a2fe7e63bb73f5a59e57b0430d2"},
+]
+
+[package.extras]
+all = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "fs (>=2.2.0,<3)", "lxml (>=4.0,<5)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.0.0)", "xattr", "zopfli (>=0.1.4)"]
+graphite = ["lz4 (>=1.7.4.2)"]
+interpolatable = ["munkres", "scipy"]
+lxml = ["lxml (>=4.0,<5)"]
+pathops = ["skia-pathops (>=0.5.0)"]
+plot = ["matplotlib"]
+repacker = ["uharfbuzz (>=0.23.0)"]
+symfont = ["sympy"]
+type1 = ["xattr"]
+ufo = ["fs (>=2.2.0,<3)"]
+unicode = ["unicodedata2 (>=15.0.0)"]
+woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"]
+
+[[package]]
+name = "furo"
+version = "2022.4.7"
+description = "A clean customisable Sphinx documentation theme."
+optional = false
+python-versions = ">=3.6"
+files = [
+    {file = "furo-2022.4.7-py3-none-any.whl", hash = "sha256:7f3e3d2fb977483590f8ecb2c2cd511bd82661b79c18efb24de9558bc9cdf2d7"},
+    {file = "furo-2022.4.7.tar.gz", hash = "sha256:96204ab7cd047e4b6c523996e0279c4c629a8fc31f4f109b2efd470c17f49c80"},
+]
+
+[package.dependencies]
+beautifulsoup4 = "*"
+pygments = ">=2.7,<3.0"
+sphinx = ">=4.0,<5.0"
+
+[[package]]
+name = "greenlet"
+version = "2.0.2"
+description = "Lightweight in-process concurrent programming"
+optional = false
+python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*"
+files = [
+    {file = "greenlet-2.0.2-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:bdfea8c661e80d3c1c99ad7c3ff74e6e87184895bbaca6ee8cc61209f8b9b85d"},
+    {file = "greenlet-2.0.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:9d14b83fab60d5e8abe587d51c75b252bcc21683f24699ada8fb275d7712f5a9"},
+    {file = "greenlet-2.0.2-cp27-cp27m-win32.whl", hash = "sha256:6c3acb79b0bfd4fe733dff8bc62695283b57949ebcca05ae5c129eb606ff2d74"},
+    {file = "greenlet-2.0.2-cp27-cp27m-win_amd64.whl", hash = "sha256:283737e0da3f08bd637b5ad058507e578dd462db259f7f6e4c5c365ba4ee9343"},
+    {file = "greenlet-2.0.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:d27ec7509b9c18b6d73f2f5ede2622441de812e7b1a80bbd446cb0633bd3d5ae"},
+    {file = "greenlet-2.0.2-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:30bcf80dda7f15ac77ba5af2b961bdd9dbc77fd4ac6105cee85b0d0a5fcf74df"},
+    {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26fbfce90728d82bc9e6c38ea4d038cba20b7faf8a0ca53a9c07b67318d46088"},
+    {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9190f09060ea4debddd24665d6804b995a9c122ef5917ab26e1566dcc712ceeb"},
+    {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d75209eed723105f9596807495d58d10b3470fa6732dd6756595e89925ce2470"},
+    {file = "greenlet-2.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3a51c9751078733d88e013587b108f1b7a1fb106d402fb390740f002b6f6551a"},
+    {file = "greenlet-2.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:76ae285c8104046b3a7f06b42f29c7b73f77683df18c49ab5af7983994c2dd91"},
+    {file = "greenlet-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:2d4686f195e32d36b4d7cf2d166857dbd0ee9f3d20ae349b6bf8afc8485b3645"},
+    {file = "greenlet-2.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c4302695ad8027363e96311df24ee28978162cdcdd2006476c43970b384a244c"},
+    {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c48f54ef8e05f04d6eff74b8233f6063cb1ed960243eacc474ee73a2ea8573ca"},
+    {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a1846f1b999e78e13837c93c778dcfc3365902cfb8d1bdb7dd73ead37059f0d0"},
+    {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a06ad5312349fec0ab944664b01d26f8d1f05009566339ac6f63f56589bc1a2"},
+    {file = "greenlet-2.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:eff4eb9b7eb3e4d0cae3d28c283dc16d9bed6b193c2e1ace3ed86ce48ea8df19"},
+    {file = "greenlet-2.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5454276c07d27a740c5892f4907c86327b632127dd9abec42ee62e12427ff7e3"},
+    {file = "greenlet-2.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:7cafd1208fdbe93b67c7086876f061f660cfddc44f404279c1585bbf3cdc64c5"},
+    {file = "greenlet-2.0.2-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:910841381caba4f744a44bf81bfd573c94e10b3045ee00de0cbf436fe50673a6"},
+    {file = "greenlet-2.0.2-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:18a7f18b82b52ee85322d7a7874e676f34ab319b9f8cce5de06067384aa8ff43"},
+    {file = "greenlet-2.0.2-cp35-cp35m-win32.whl", hash = "sha256:03a8f4f3430c3b3ff8d10a2a86028c660355ab637cee9333d63d66b56f09d52a"},
+    {file = "greenlet-2.0.2-cp35-cp35m-win_amd64.whl", hash = "sha256:4b58adb399c4d61d912c4c331984d60eb66565175cdf4a34792cd9600f21b394"},
+    {file = "greenlet-2.0.2-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:703f18f3fda276b9a916f0934d2fb6d989bf0b4fb5a64825260eb9bfd52d78f0"},
+    {file = "greenlet-2.0.2-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:32e5b64b148966d9cccc2c8d35a671409e45f195864560829f395a54226408d3"},
+    {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2dd11f291565a81d71dab10b7033395b7a3a5456e637cf997a6f33ebdf06f8db"},
+    {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e0f72c9ddb8cd28532185f54cc1453f2c16fb417a08b53a855c4e6a418edd099"},
+    {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd021c754b162c0fb55ad5d6b9d960db667faad0fa2ff25bb6e1301b0b6e6a75"},
+    {file = "greenlet-2.0.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:3c9b12575734155d0c09d6c3e10dbd81665d5c18e1a7c6597df72fd05990c8cf"},
+    {file = "greenlet-2.0.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b9ec052b06a0524f0e35bd8790686a1da006bd911dd1ef7d50b77bfbad74e292"},
+    {file = "greenlet-2.0.2-cp36-cp36m-win32.whl", hash = "sha256:dbfcfc0218093a19c252ca8eb9aee3d29cfdcb586df21049b9d777fd32c14fd9"},
+    {file = "greenlet-2.0.2-cp36-cp36m-win_amd64.whl", hash = "sha256:9f35ec95538f50292f6d8f2c9c9f8a3c6540bbfec21c9e5b4b751e0a7c20864f"},
+    {file = "greenlet-2.0.2-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:d5508f0b173e6aa47273bdc0a0b5ba055b59662ba7c7ee5119528f466585526b"},
+    {file = "greenlet-2.0.2-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:f82d4d717d8ef19188687aa32b8363e96062911e63ba22a0cff7802a8e58e5f1"},
+    {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9c59a2120b55788e800d82dfa99b9e156ff8f2227f07c5e3012a45a399620b7"},
+    {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2780572ec463d44c1d3ae850239508dbeb9fed38e294c68d19a24d925d9223ca"},
+    {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:937e9020b514ceedb9c830c55d5c9872abc90f4b5862f89c0887033ae33c6f73"},
+    {file = "greenlet-2.0.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:36abbf031e1c0f79dd5d596bfaf8e921c41df2bdf54ee1eed921ce1f52999a86"},
+    {file = "greenlet-2.0.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:18e98fb3de7dba1c0a852731c3070cf022d14f0d68b4c87a19cc1016f3bb8b33"},
+    {file = "greenlet-2.0.2-cp37-cp37m-win32.whl", hash = "sha256:3f6ea9bd35eb450837a3d80e77b517ea5bc56b4647f5502cd28de13675ee12f7"},
+    {file = "greenlet-2.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:7492e2b7bd7c9b9916388d9df23fa49d9b88ac0640db0a5b4ecc2b653bf451e3"},
+    {file = "greenlet-2.0.2-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:b864ba53912b6c3ab6bcb2beb19f19edd01a6bfcbdfe1f37ddd1778abfe75a30"},
+    {file = "greenlet-2.0.2-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:ba2956617f1c42598a308a84c6cf021a90ff3862eddafd20c3333d50f0edb45b"},
+    {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc3a569657468b6f3fb60587e48356fe512c1754ca05a564f11366ac9e306526"},
+    {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8eab883b3b2a38cc1e050819ef06a7e6344d4a990d24d45bc6f2cf959045a45b"},
+    {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acd2162a36d3de67ee896c43effcd5ee3de247eb00354db411feb025aa319857"},
+    {file = "greenlet-2.0.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0bf60faf0bc2468089bdc5edd10555bab6e85152191df713e2ab1fcc86382b5a"},
+    {file = "greenlet-2.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b0ef99cdbe2b682b9ccbb964743a6aca37905fda5e0452e5ee239b1654d37f2a"},
+    {file = "greenlet-2.0.2-cp38-cp38-win32.whl", hash = "sha256:b80f600eddddce72320dbbc8e3784d16bd3fb7b517e82476d8da921f27d4b249"},
+    {file = "greenlet-2.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:4d2e11331fc0c02b6e84b0d28ece3a36e0548ee1a1ce9ddde03752d9b79bba40"},
+    {file = "greenlet-2.0.2-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:88d9ab96491d38a5ab7c56dd7a3cc37d83336ecc564e4e8816dbed12e5aaefc8"},
+    {file = "greenlet-2.0.2-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:561091a7be172ab497a3527602d467e2b3fbe75f9e783d8b8ce403fa414f71a6"},
+    {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:971ce5e14dc5e73715755d0ca2975ac88cfdaefcaab078a284fea6cfabf866df"},
+    {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be4ed120b52ae4d974aa40215fcdfde9194d63541c7ded40ee12eb4dda57b76b"},
+    {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94c817e84245513926588caf1152e3b559ff794d505555211ca041f032abbb6b"},
+    {file = "greenlet-2.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1a819eef4b0e0b96bb0d98d797bef17dc1b4a10e8d7446be32d1da33e095dbb8"},
+    {file = "greenlet-2.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7efde645ca1cc441d6dc4b48c0f7101e8d86b54c8530141b09fd31cef5149ec9"},
+    {file = "greenlet-2.0.2-cp39-cp39-win32.whl", hash = "sha256:ea9872c80c132f4663822dd2a08d404073a5a9b5ba6155bea72fb2a79d1093b5"},
+    {file = "greenlet-2.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:db1a39669102a1d8d12b57de2bb7e2ec9066a6f2b3da35ae511ff93b01b5d564"},
+    {file = "greenlet-2.0.2.tar.gz", hash = "sha256:e7c8dc13af7db097bed64a051d2dd49e9f0af495c26995c00a9ee842690d34c0"},
+]
+
+[package.extras]
+docs = ["Sphinx", "docutils (<0.18)"]
+test = ["objgraph", "psutil"]
+
+[[package]]
+name = "h11"
+version = "0.14.0"
+description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
+optional = false
+python-versions = ">=3.7"
+files = [
+    {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"},
+    {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"},
+]
+
+[[package]]
+name = "html5tagger"
+version = "1.3.0"
+description = "Pythonic HTML generation/templating (no template files)"
+optional = false
+python-versions = ">=3.7"
+files = [
+    {file = "html5tagger-1.3.0-py3-none-any.whl", hash = "sha256:ce14313515edffec8ed8a36c5890d023922641171b4e6e5774ad1a74998f5351"},
+    {file = "html5tagger-1.3.0.tar.gz", hash = "sha256:84fa3dfb49e5c83b79bbd856ab7b1de8e2311c3bb46a8be925f119e3880a8da9"},
+]
+
+[[package]]
+name = "httptools"
+version = "0.5.0"
+description = "A collection of framework independent HTTP protocol utils."
+optional = false
+python-versions = ">=3.5.0"
+files = [
+    {file = "httptools-0.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8f470c79061599a126d74385623ff4744c4e0f4a0997a353a44923c0b561ee51"},
+    {file = "httptools-0.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e90491a4d77d0cb82e0e7a9cb35d86284c677402e4ce7ba6b448ccc7325c5421"},
+    {file = "httptools-0.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1d2357f791b12d86faced7b5736dea9ef4f5ecdc6c3f253e445ee82da579449"},
+    {file = "httptools-0.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f90cd6fd97c9a1b7fe9215e60c3bd97336742a0857f00a4cb31547bc22560c2"},
+    {file = "httptools-0.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5230a99e724a1bdbbf236a1b58d6e8504b912b0552721c7c6b8570925ee0ccde"},
+    {file = "httptools-0.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3a47a34f6015dd52c9eb629c0f5a8a5193e47bf2a12d9a3194d231eaf1bc451a"},
+    {file = "httptools-0.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:24bb4bb8ac3882f90aa95403a1cb48465de877e2d5298ad6ddcfdebec060787d"},
+    {file = "httptools-0.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e67d4f8734f8054d2c4858570cc4b233bf753f56e85217de4dfb2495904cf02e"},
+    {file = "httptools-0.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7e5eefc58d20e4c2da82c78d91b2906f1a947ef42bd668db05f4ab4201a99f49"},
+    {file = "httptools-0.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0297822cea9f90a38df29f48e40b42ac3d48a28637368f3ec6d15eebefd182f9"},
+    {file = "httptools-0.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:557be7fbf2bfa4a2ec65192c254e151684545ebab45eca5d50477d562c40f986"},
+    {file = "httptools-0.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:54465401dbbec9a6a42cf737627fb0f014d50dc7365a6b6cd57753f151a86ff0"},
+    {file = "httptools-0.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4d9ebac23d2de960726ce45f49d70eb5466725c0087a078866043dad115f850f"},
+    {file = "httptools-0.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:e8a34e4c0ab7b1ca17b8763613783e2458e77938092c18ac919420ab8655c8c1"},
+    {file = "httptools-0.5.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f659d7a48401158c59933904040085c200b4be631cb5f23a7d561fbae593ec1f"},
+    {file = "httptools-0.5.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef1616b3ba965cd68e6f759eeb5d34fbf596a79e84215eeceebf34ba3f61fdc7"},
+    {file = "httptools-0.5.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3625a55886257755cb15194efbf209584754e31d336e09e2ffe0685a76cb4b60"},
+    {file = "httptools-0.5.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:72ad589ba5e4a87e1d404cc1cb1b5780bfcb16e2aec957b88ce15fe879cc08ca"},
+    {file = "httptools-0.5.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:850fec36c48df5a790aa735417dca8ce7d4b48d59b3ebd6f83e88a8125cde324"},
+    {file = "httptools-0.5.0-cp36-cp36m-win_amd64.whl", hash = "sha256:f222e1e9d3f13b68ff8a835574eda02e67277d51631d69d7cf7f8e07df678c86"},
+    {file = "httptools-0.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3cb8acf8f951363b617a8420768a9f249099b92e703c052f9a51b66342eea89b"},
+    {file = "httptools-0.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:550059885dc9c19a072ca6d6735739d879be3b5959ec218ba3e013fd2255a11b"},
+    {file = "httptools-0.5.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a04fe458a4597aa559b79c7f48fe3dceabef0f69f562daf5c5e926b153817281"},
+    {file = "httptools-0.5.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7d0c1044bce274ec6711f0770fd2d5544fe392591d204c68328e60a46f88843b"},
+    {file = "httptools-0.5.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c6eeefd4435055a8ebb6c5cc36111b8591c192c56a95b45fe2af22d9881eee25"},
+    {file = "httptools-0.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:5b65be160adcd9de7a7e6413a4966665756e263f0d5ddeffde277ffeee0576a5"},
+    {file = "httptools-0.5.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fe9c766a0c35b7e3d6b6939393c8dfdd5da3ac5dec7f971ec9134f284c6c36d6"},
+    {file = "httptools-0.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:85b392aba273566c3d5596a0a490978c085b79700814fb22bfd537d381dd230c"},
+    {file = "httptools-0.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f5e3088f4ed33947e16fd865b8200f9cfae1144f41b64a8cf19b599508e096bc"},
+    {file = "httptools-0.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c2a56b6aad7cc8f5551d8e04ff5a319d203f9d870398b94702300de50190f63"},
+    {file = "httptools-0.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9b571b281a19762adb3f48a7731f6842f920fa71108aff9be49888320ac3e24d"},
+    {file = "httptools-0.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aa47ffcf70ba6f7848349b8a6f9b481ee0f7637931d91a9860a1838bfc586901"},
+    {file = "httptools-0.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:bede7ee075e54b9a5bde695b4fc8f569f30185891796b2e4e09e2226801d09bd"},
+    {file = "httptools-0.5.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:64eba6f168803a7469866a9c9b5263a7463fa8b7a25b35e547492aa7322036b6"},
+    {file = "httptools-0.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4b098e4bb1174096a93f48f6193e7d9aa7071506a5877da09a783509ca5fff42"},
+    {file = "httptools-0.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9423a2de923820c7e82e18980b937893f4aa8251c43684fa1772e341f6e06887"},
+    {file = "httptools-0.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca1b7becf7d9d3ccdbb2f038f665c0f4857e08e1d8481cbcc1a86a0afcfb62b2"},
+    {file = "httptools-0.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:50d4613025f15f4b11f1c54bbed4761c0020f7f921b95143ad6d58c151198142"},
+    {file = "httptools-0.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8ffce9d81c825ac1deaa13bc9694c0562e2840a48ba21cfc9f3b4c922c16f372"},
+    {file = "httptools-0.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:1af91b3650ce518d226466f30bbba5b6376dbd3ddb1b2be8b0658c6799dd450b"},
+    {file = "httptools-0.5.0.tar.gz", hash = "sha256:295874861c173f9101960bba332429bb77ed4dcd8cdf5cee9922eb00e4f6bc09"},
+]
+
+[package.extras]
+test = ["Cython (>=0.29.24,<0.30.0)"]
+
+[[package]]
+name = "idna"
+version = "3.4"
+description = "Internationalized Domain Names in Applications (IDNA)"
+optional = false
+python-versions = ">=3.5"
+files = [
+    {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"},
+    {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"},
+]
+
+[[package]]
+name = "imagesize"
+version = "1.4.1"
+description = "Getting image size from png/jpeg/jpeg2000/gif file"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+files = [
+    {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"},
+    {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"},
+]
+
+[[package]]
+name = "importlib-metadata"
+version = "6.6.0"
+description = "Read metadata from Python packages"
+optional = false
+python-versions = ">=3.7"
+files = [
+    {file = "importlib_metadata-6.6.0-py3-none-any.whl", hash = "sha256:43dd286a2cd8995d5eaef7fee2066340423b818ed3fd70adf0bad5f1fac53fed"},
+    {file = "importlib_metadata-6.6.0.tar.gz", hash = "sha256:92501cdf9cc66ebd3e612f1b4f0c0765dfa42f0fa38ffb319b6bd84dd675d705"},
+]
+
+[package.dependencies]
+zipp = ">=0.5"
+
+[package.extras]
+docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
+perf = ["ipython"]
+testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"]
+
+[[package]]
+name = "importlib-resources"
+version = "5.12.0"
+description = "Read resources from Python packages"
+optional = false
+python-versions = ">=3.7"
+files = [
+    {file = "importlib_resources-5.12.0-py3-none-any.whl", hash = "sha256:7b1deeebbf351c7578e09bf2f63fa2ce8b5ffec296e0d349139d43cca061a81a"},
+    {file = "importlib_resources-5.12.0.tar.gz", hash = "sha256:4be82589bf5c1d7999aedf2a45159d10cb3ca4f19b2271f8792bc8e6da7b22f6"},
+]
+
+[package.dependencies]
+zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""}
+
+[package.extras]
+docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
+testing = ["flake8 (<5)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"]
+
+[[package]]
+name = "itsdangerous"
+version = "2.1.2"
+description = "Safely pass data to untrusted environments and back."
+optional = false
+python-versions = ">=3.7"
+files = [
+    {file = "itsdangerous-2.1.2-py3-none-any.whl", hash = "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44"},
+    {file = "itsdangerous-2.1.2.tar.gz", hash = "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a"},
+]
+
+[[package]]
+name = "jinja2"
+version = "3.1.2"
+description = "A very fast and expressive template engine."
+optional = false
+python-versions = ">=3.7"
+files = [
+    {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"},
+    {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"},
+]
+
+[package.dependencies]
+MarkupSafe = ">=2.0"
+
+[package.extras]
+i18n = ["Babel (>=2.7)"]
+
+[[package]]
+name = "jsonpatch"
+version = "1.32"
+description = "Apply JSON-Patches (RFC 6902)"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+files = [
+    {file = "jsonpatch-1.32-py2.py3-none-any.whl", hash = "sha256:26ac385719ac9f54df8a2f0827bb8253aa3ea8ab7b3368457bcdb8c14595a397"},
+    {file = "jsonpatch-1.32.tar.gz", hash = "sha256:b6ddfe6c3db30d81a96aaeceb6baf916094ffa23d7dd5fa2c13e13f8b6e600c2"},
+]
+
+[package.dependencies]
+jsonpointer = ">=1.9"
+
+[[package]]
+name = "jsonpointer"
+version = "2.3"
+description = "Identify specific nodes in a JSON document (RFC 6901)"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+files = [
+    {file = "jsonpointer-2.3-py2.py3-none-any.whl", hash = "sha256:51801e558539b4e9cd268638c078c6c5746c9ac96bc38152d443400e4f3793e9"},
+    {file = "jsonpointer-2.3.tar.gz", hash = "sha256:97cba51526c829282218feb99dab1b1e6bdf8efd1c43dc9d57be093c0d69c99a"},
+]
+
+[[package]]
+name = "kiwisolver"
+version = "1.4.4"
+description = "A fast implementation of the Cassowary constraint solver"
+optional = false
+python-versions = ">=3.7"
+files = [
+    {file = "kiwisolver-1.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2f5e60fabb7343a836360c4f0919b8cd0d6dbf08ad2ca6b9cf90bf0c76a3c4f6"},
+    {file = "kiwisolver-1.4.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:10ee06759482c78bdb864f4109886dff7b8a56529bc1609d4f1112b93fe6423c"},
+    {file = "kiwisolver-1.4.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c79ebe8f3676a4c6630fd3f777f3cfecf9289666c84e775a67d1d358578dc2e3"},
+    {file = "kiwisolver-1.4.4-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:abbe9fa13da955feb8202e215c4018f4bb57469b1b78c7a4c5c7b93001699938"},
+    {file = "kiwisolver-1.4.4-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7577c1987baa3adc4b3c62c33bd1118c3ef5c8ddef36f0f2c950ae0b199e100d"},
+    {file = "kiwisolver-1.4.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8ad8285b01b0d4695102546b342b493b3ccc6781fc28c8c6a1bb63e95d22f09"},
+    {file = "kiwisolver-1.4.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8ed58b8acf29798b036d347791141767ccf65eee7f26bde03a71c944449e53de"},
+    {file = "kiwisolver-1.4.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a68b62a02953b9841730db7797422f983935aeefceb1679f0fc85cbfbd311c32"},
+    {file = "kiwisolver-1.4.4-cp310-cp310-win32.whl", hash = "sha256:e92a513161077b53447160b9bd8f522edfbed4bd9759e4c18ab05d7ef7e49408"},
+    {file = "kiwisolver-1.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:3fe20f63c9ecee44560d0e7f116b3a747a5d7203376abeea292ab3152334d004"},
+    {file = "kiwisolver-1.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e0ea21f66820452a3f5d1655f8704a60d66ba1191359b96541eaf457710a5fc6"},
+    {file = "kiwisolver-1.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bc9db8a3efb3e403e4ecc6cd9489ea2bac94244f80c78e27c31dcc00d2790ac2"},
+    {file = "kiwisolver-1.4.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d5b61785a9ce44e5a4b880272baa7cf6c8f48a5180c3e81c59553ba0cb0821ca"},
+    {file = "kiwisolver-1.4.4-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c2dbb44c3f7e6c4d3487b31037b1bdbf424d97687c1747ce4ff2895795c9bf69"},
+    {file = "kiwisolver-1.4.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6295ecd49304dcf3bfbfa45d9a081c96509e95f4b9d0eb7ee4ec0530c4a96514"},
+    {file = "kiwisolver-1.4.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4bd472dbe5e136f96a4b18f295d159d7f26fd399136f5b17b08c4e5f498cd494"},
+    {file = "kiwisolver-1.4.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bf7d9fce9bcc4752ca4a1b80aabd38f6d19009ea5cbda0e0856983cf6d0023f5"},
+    {file = "kiwisolver-1.4.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78d6601aed50c74e0ef02f4204da1816147a6d3fbdc8b3872d263338a9052c51"},
+    {file = "kiwisolver-1.4.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:877272cf6b4b7e94c9614f9b10140e198d2186363728ed0f701c6eee1baec1da"},
+    {file = "kiwisolver-1.4.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:db608a6757adabb32f1cfe6066e39b3706d8c3aa69bbc353a5b61edad36a5cb4"},
+    {file = "kiwisolver-1.4.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:5853eb494c71e267912275e5586fe281444eb5e722de4e131cddf9d442615626"},
+    {file = "kiwisolver-1.4.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:f0a1dbdb5ecbef0d34eb77e56fcb3e95bbd7e50835d9782a45df81cc46949750"},
+    {file = "kiwisolver-1.4.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:283dffbf061a4ec60391d51e6155e372a1f7a4f5b15d59c8505339454f8989e4"},
+    {file = "kiwisolver-1.4.4-cp311-cp311-win32.whl", hash = "sha256:d06adcfa62a4431d404c31216f0f8ac97397d799cd53800e9d3efc2fbb3cf14e"},
+    {file = "kiwisolver-1.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:e7da3fec7408813a7cebc9e4ec55afed2d0fd65c4754bc376bf03498d4e92686"},
+    {file = "kiwisolver-1.4.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:62ac9cc684da4cf1778d07a89bf5f81b35834cb96ca523d3a7fb32509380cbf6"},
+    {file = "kiwisolver-1.4.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41dae968a94b1ef1897cb322b39360a0812661dba7c682aa45098eb8e193dbdf"},
+    {file = "kiwisolver-1.4.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:02f79693ec433cb4b5f51694e8477ae83b3205768a6fb48ffba60549080e295b"},
+    {file = "kiwisolver-1.4.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d0611a0a2a518464c05ddd5a3a1a0e856ccc10e67079bb17f265ad19ab3c7597"},
+    {file = "kiwisolver-1.4.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:db5283d90da4174865d520e7366801a93777201e91e79bacbac6e6927cbceede"},
+    {file = "kiwisolver-1.4.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1041feb4cda8708ce73bb4dcb9ce1ccf49d553bf87c3954bdfa46f0c3f77252c"},
+    {file = "kiwisolver-1.4.4-cp37-cp37m-win32.whl", hash = "sha256:a553dadda40fef6bfa1456dc4be49b113aa92c2a9a9e8711e955618cd69622e3"},
+    {file = "kiwisolver-1.4.4-cp37-cp37m-win_amd64.whl", hash = "sha256:03baab2d6b4a54ddbb43bba1a3a2d1627e82d205c5cf8f4c924dc49284b87166"},
+    {file = "kiwisolver-1.4.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:841293b17ad704d70c578f1f0013c890e219952169ce8a24ebc063eecf775454"},
+    {file = "kiwisolver-1.4.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f4f270de01dd3e129a72efad823da90cc4d6aafb64c410c9033aba70db9f1ff0"},
+    {file = "kiwisolver-1.4.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f9f39e2f049db33a908319cf46624a569b36983c7c78318e9726a4cb8923b26c"},
+    {file = "kiwisolver-1.4.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c97528e64cb9ebeff9701e7938653a9951922f2a38bd847787d4a8e498cc83ae"},
+    {file = "kiwisolver-1.4.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d1573129aa0fd901076e2bfb4275a35f5b7aa60fbfb984499d661ec950320b0"},
+    {file = "kiwisolver-1.4.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ad881edc7ccb9d65b0224f4e4d05a1e85cf62d73aab798943df6d48ab0cd79a1"},
+    {file = "kiwisolver-1.4.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b428ef021242344340460fa4c9185d0b1f66fbdbfecc6c63eff4b7c29fad429d"},
+    {file = "kiwisolver-1.4.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:2e407cb4bd5a13984a6c2c0fe1845e4e41e96f183e5e5cd4d77a857d9693494c"},
+    {file = "kiwisolver-1.4.4-cp38-cp38-win32.whl", hash = "sha256:75facbe9606748f43428fc91a43edb46c7ff68889b91fa31f53b58894503a191"},
+    {file = "kiwisolver-1.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:5bce61af018b0cb2055e0e72e7d65290d822d3feee430b7b8203d8a855e78766"},
+    {file = "kiwisolver-1.4.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8c808594c88a025d4e322d5bb549282c93c8e1ba71b790f539567932722d7bd8"},
+    {file = "kiwisolver-1.4.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f0a71d85ecdd570ded8ac3d1c0f480842f49a40beb423bb8014539a9f32a5897"},
+    {file = "kiwisolver-1.4.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b533558eae785e33e8c148a8d9921692a9fe5aa516efbdff8606e7d87b9d5824"},
+    {file = "kiwisolver-1.4.4-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:efda5fc8cc1c61e4f639b8067d118e742b812c930f708e6667a5ce0d13499e29"},
+    {file = "kiwisolver-1.4.4-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7c43e1e1206cd421cd92e6b3280d4385d41d7166b3ed577ac20444b6995a445f"},
+    {file = "kiwisolver-1.4.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc8d3bd6c72b2dd9decf16ce70e20abcb3274ba01b4e1c96031e0c4067d1e7cd"},
+    {file = "kiwisolver-1.4.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4ea39b0ccc4f5d803e3337dd46bcce60b702be4d86fd0b3d7531ef10fd99a1ac"},
+    {file = "kiwisolver-1.4.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:968f44fdbf6dd757d12920d63b566eeb4d5b395fd2d00d29d7ef00a00582aac9"},
+    {file = "kiwisolver-1.4.4-cp39-cp39-win32.whl", hash = "sha256:da7e547706e69e45d95e116e6939488d62174e033b763ab1496b4c29b76fabea"},
+    {file = "kiwisolver-1.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:ba59c92039ec0a66103b1d5fe588fa546373587a7d68f5c96f743c3396afc04b"},
+    {file = "kiwisolver-1.4.4-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:91672bacaa030f92fc2f43b620d7b337fd9a5af28b0d6ed3f77afc43c4a64b5a"},
+    {file = "kiwisolver-1.4.4-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:787518a6789009c159453da4d6b683f468ef7a65bbde796bcea803ccf191058d"},
+    {file = "kiwisolver-1.4.4-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da152d8cdcab0e56e4f45eb08b9aea6455845ec83172092f09b0e077ece2cf7a"},
+    {file = "kiwisolver-1.4.4-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:ecb1fa0db7bf4cff9dac752abb19505a233c7f16684c5826d1f11ebd9472b871"},
+    {file = "kiwisolver-1.4.4-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:28bc5b299f48150b5f822ce68624e445040595a4ac3d59251703779836eceff9"},
+    {file = "kiwisolver-1.4.4-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:81e38381b782cc7e1e46c4e14cd997ee6040768101aefc8fa3c24a4cc58e98f8"},
+    {file = "kiwisolver-1.4.4-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2a66fdfb34e05b705620dd567f5a03f239a088d5a3f321e7b6ac3239d22aa286"},
+    {file = "kiwisolver-1.4.4-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:872b8ca05c40d309ed13eb2e582cab0c5a05e81e987ab9c521bf05ad1d5cf5cb"},
+    {file = "kiwisolver-1.4.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:70e7c2e7b750585569564e2e5ca9845acfaa5da56ac46df68414f29fea97be9f"},
+    {file = "kiwisolver-1.4.4-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9f85003f5dfa867e86d53fac6f7e6f30c045673fa27b603c397753bebadc3008"},
+    {file = "kiwisolver-1.4.4-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e307eb9bd99801f82789b44bb45e9f541961831c7311521b13a6c85afc09767"},
+    {file = "kiwisolver-1.4.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1792d939ec70abe76f5054d3f36ed5656021dcad1322d1cc996d4e54165cef9"},
+    {file = "kiwisolver-1.4.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6cb459eea32a4e2cf18ba5fcece2dbdf496384413bc1bae15583f19e567f3b2"},
+    {file = "kiwisolver-1.4.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:36dafec3d6d6088d34e2de6b85f9d8e2324eb734162fba59d2ba9ed7a2043d5b"},
+    {file = "kiwisolver-1.4.4.tar.gz", hash = "sha256:d41997519fcba4a1e46eb4a2fe31bc12f0ff957b2b81bac28db24744f333e955"},
+]
+
+[[package]]
+name = "livereload"
+version = "2.6.3"
+description = "Python LiveReload is an awesome tool for web developers"
+optional = false
+python-versions = "*"
+files = [
+    {file = "livereload-2.6.3-py2.py3-none-any.whl", hash = "sha256:ad4ac6f53b2d62bb6ce1a5e6e96f1f00976a32348afedcb4b6d68df2a1d346e4"},
+    {file = "livereload-2.6.3.tar.gz", hash = "sha256:776f2f865e59fde56490a56bcc6773b6917366bce0c267c60ee8aaf1a0959869"},
+]
+
+[package.dependencies]
+six = "*"
+tornado = {version = "*", markers = "python_version > \"2.7\""}
+
+[[package]]
+name = "lxml"
+version = "4.9.2"
+description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API."
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*"
+files = [
+    {file = "lxml-4.9.2-cp27-cp27m-macosx_10_15_x86_64.whl", hash = "sha256:76cf573e5a365e790396a5cc2b909812633409306c6531a6877c59061e42c4f2"},
+    {file = "lxml-4.9.2-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b1f42b6921d0e81b1bcb5e395bc091a70f41c4d4e55ba99c6da2b31626c44892"},
+    {file = "lxml-4.9.2-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:9f102706d0ca011de571de32c3247c6476b55bb6bc65a20f682f000b07a4852a"},
+    {file = "lxml-4.9.2-cp27-cp27m-win32.whl", hash = "sha256:8d0b4612b66ff5d62d03bcaa043bb018f74dfea51184e53f067e6fdcba4bd8de"},
+    {file = "lxml-4.9.2-cp27-cp27m-win_amd64.whl", hash = "sha256:4c8f293f14abc8fd3e8e01c5bd86e6ed0b6ef71936ded5bf10fe7a5efefbaca3"},
+    {file = "lxml-4.9.2-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2899456259589aa38bfb018c364d6ae7b53c5c22d8e27d0ec7609c2a1ff78b50"},
+    {file = "lxml-4.9.2-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6749649eecd6a9871cae297bffa4ee76f90b4504a2a2ab528d9ebe912b101975"},
+    {file = "lxml-4.9.2-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:a08cff61517ee26cb56f1e949cca38caabe9ea9fbb4b1e10a805dc39844b7d5c"},
+    {file = "lxml-4.9.2-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:85cabf64adec449132e55616e7ca3e1000ab449d1d0f9d7f83146ed5bdcb6d8a"},
+    {file = "lxml-4.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:8340225bd5e7a701c0fa98284c849c9b9fc9238abf53a0ebd90900f25d39a4e4"},
+    {file = "lxml-4.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:1ab8f1f932e8f82355e75dda5413a57612c6ea448069d4fb2e217e9a4bed13d4"},
+    {file = "lxml-4.9.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:699a9af7dffaf67deeae27b2112aa06b41c370d5e7633e0ee0aea2e0b6c211f7"},
+    {file = "lxml-4.9.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b9cc34af337a97d470040f99ba4282f6e6bac88407d021688a5d585e44a23184"},
+    {file = "lxml-4.9.2-cp310-cp310-win32.whl", hash = "sha256:d02a5399126a53492415d4906ab0ad0375a5456cc05c3fc0fc4ca11771745cda"},
+    {file = "lxml-4.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:a38486985ca49cfa574a507e7a2215c0c780fd1778bb6290c21193b7211702ab"},
+    {file = "lxml-4.9.2-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:c83203addf554215463b59f6399835201999b5e48019dc17f182ed5ad87205c9"},
+    {file = "lxml-4.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:2a87fa548561d2f4643c99cd13131acb607ddabb70682dcf1dff5f71f781a4bf"},
+    {file = "lxml-4.9.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:d6b430a9938a5a5d85fc107d852262ddcd48602c120e3dbb02137c83d212b380"},
+    {file = "lxml-4.9.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:3efea981d956a6f7173b4659849f55081867cf897e719f57383698af6f618a92"},
+    {file = "lxml-4.9.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:df0623dcf9668ad0445e0558a21211d4e9a149ea8f5666917c8eeec515f0a6d1"},
+    {file = "lxml-4.9.2-cp311-cp311-win32.whl", hash = "sha256:da248f93f0418a9e9d94b0080d7ebc407a9a5e6d0b57bb30db9b5cc28de1ad33"},
+    {file = "lxml-4.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:3818b8e2c4b5148567e1b09ce739006acfaa44ce3156f8cbbc11062994b8e8dd"},
+    {file = "lxml-4.9.2-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ca989b91cf3a3ba28930a9fc1e9aeafc2a395448641df1f387a2d394638943b0"},
+    {file = "lxml-4.9.2-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:822068f85e12a6e292803e112ab876bc03ed1f03dddb80154c395f891ca6b31e"},
+    {file = "lxml-4.9.2-cp35-cp35m-win32.whl", hash = "sha256:be7292c55101e22f2a3d4d8913944cbea71eea90792bf914add27454a13905df"},
+    {file = "lxml-4.9.2-cp35-cp35m-win_amd64.whl", hash = "sha256:998c7c41910666d2976928c38ea96a70d1aa43be6fe502f21a651e17483a43c5"},
+    {file = "lxml-4.9.2-cp36-cp36m-macosx_10_15_x86_64.whl", hash = "sha256:b26a29f0b7fc6f0897f043ca366142d2b609dc60756ee6e4e90b5f762c6adc53"},
+    {file = "lxml-4.9.2-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:ab323679b8b3030000f2be63e22cdeea5b47ee0abd2d6a1dc0c8103ddaa56cd7"},
+    {file = "lxml-4.9.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:689bb688a1db722485e4610a503e3e9210dcc20c520b45ac8f7533c837be76fe"},
+    {file = "lxml-4.9.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:f49e52d174375a7def9915c9f06ec4e569d235ad428f70751765f48d5926678c"},
+    {file = "lxml-4.9.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:36c3c175d34652a35475a73762b545f4527aec044910a651d2bf50de9c3352b1"},
+    {file = "lxml-4.9.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a35f8b7fa99f90dd2f5dc5a9fa12332642f087a7641289ca6c40d6e1a2637d8e"},
+    {file = "lxml-4.9.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:58bfa3aa19ca4c0f28c5dde0ff56c520fbac6f0daf4fac66ed4c8d2fb7f22e74"},
+    {file = "lxml-4.9.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:bc718cd47b765e790eecb74d044cc8d37d58562f6c314ee9484df26276d36a38"},
+    {file = "lxml-4.9.2-cp36-cp36m-win32.whl", hash = "sha256:d5bf6545cd27aaa8a13033ce56354ed9e25ab0e4ac3b5392b763d8d04b08e0c5"},
+    {file = "lxml-4.9.2-cp36-cp36m-win_amd64.whl", hash = "sha256:3ab9fa9d6dc2a7f29d7affdf3edebf6ece6fb28a6d80b14c3b2fb9d39b9322c3"},
+    {file = "lxml-4.9.2-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:05ca3f6abf5cf78fe053da9b1166e062ade3fa5d4f92b4ed688127ea7d7b1d03"},
+    {file = "lxml-4.9.2-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:a5da296eb617d18e497bcf0a5c528f5d3b18dadb3619fbdadf4ed2356ef8d941"},
+    {file = "lxml-4.9.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:04876580c050a8c5341d706dd464ff04fd597095cc8c023252566a8826505726"},
+    {file = "lxml-4.9.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:c9ec3eaf616d67db0764b3bb983962b4f385a1f08304fd30c7283954e6a7869b"},
+    {file = "lxml-4.9.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2a29ba94d065945944016b6b74e538bdb1751a1db6ffb80c9d3c2e40d6fa9894"},
+    {file = "lxml-4.9.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a82d05da00a58b8e4c0008edbc8a4b6ec5a4bc1e2ee0fb6ed157cf634ed7fa45"},
+    {file = "lxml-4.9.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:223f4232855ade399bd409331e6ca70fb5578efef22cf4069a6090acc0f53c0e"},
+    {file = "lxml-4.9.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d17bc7c2ccf49c478c5bdd447594e82692c74222698cfc9b5daae7ae7e90743b"},
+    {file = "lxml-4.9.2-cp37-cp37m-win32.whl", hash = "sha256:b64d891da92e232c36976c80ed7ebb383e3f148489796d8d31a5b6a677825efe"},
+    {file = "lxml-4.9.2-cp37-cp37m-win_amd64.whl", hash = "sha256:a0a336d6d3e8b234a3aae3c674873d8f0e720b76bc1d9416866c41cd9500ffb9"},
+    {file = "lxml-4.9.2-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:da4dd7c9c50c059aba52b3524f84d7de956f7fef88f0bafcf4ad7dde94a064e8"},
+    {file = "lxml-4.9.2-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:821b7f59b99551c69c85a6039c65b75f5683bdc63270fec660f75da67469ca24"},
+    {file = "lxml-4.9.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:e5168986b90a8d1f2f9dc1b841467c74221bd752537b99761a93d2d981e04889"},
+    {file = "lxml-4.9.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:8e20cb5a47247e383cf4ff523205060991021233ebd6f924bca927fcf25cf86f"},
+    {file = "lxml-4.9.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:13598ecfbd2e86ea7ae45ec28a2a54fb87ee9b9fdb0f6d343297d8e548392c03"},
+    {file = "lxml-4.9.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:880bbbcbe2fca64e2f4d8e04db47bcdf504936fa2b33933efd945e1b429bea8c"},
+    {file = "lxml-4.9.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7d2278d59425777cfcb19735018d897ca8303abe67cc735f9f97177ceff8027f"},
+    {file = "lxml-4.9.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5344a43228767f53a9df6e5b253f8cdca7dfc7b7aeae52551958192f56d98457"},
+    {file = "lxml-4.9.2-cp38-cp38-win32.whl", hash = "sha256:925073b2fe14ab9b87e73f9a5fde6ce6392da430f3004d8b72cc86f746f5163b"},
+    {file = "lxml-4.9.2-cp38-cp38-win_amd64.whl", hash = "sha256:9b22c5c66f67ae00c0199f6055705bc3eb3fcb08d03d2ec4059a2b1b25ed48d7"},
+    {file = "lxml-4.9.2-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:5f50a1c177e2fa3ee0667a5ab79fdc6b23086bc8b589d90b93b4bd17eb0e64d1"},
+    {file = "lxml-4.9.2-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:090c6543d3696cbe15b4ac6e175e576bcc3f1ccfbba970061b7300b0c15a2140"},
+    {file = "lxml-4.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:63da2ccc0857c311d764e7d3d90f429c252e83b52d1f8f1d1fe55be26827d1f4"},
+    {file = "lxml-4.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:5b4545b8a40478183ac06c073e81a5ce4cf01bf1734962577cf2bb569a5b3bbf"},
+    {file = "lxml-4.9.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2e430cd2824f05f2d4f687701144556646bae8f249fd60aa1e4c768ba7018947"},
+    {file = "lxml-4.9.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6804daeb7ef69e7b36f76caddb85cccd63d0c56dedb47555d2fc969e2af6a1a5"},
+    {file = "lxml-4.9.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a6e441a86553c310258aca15d1c05903aaf4965b23f3bc2d55f200804e005ee5"},
+    {file = "lxml-4.9.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ca34efc80a29351897e18888c71c6aca4a359247c87e0b1c7ada14f0ab0c0fb2"},
+    {file = "lxml-4.9.2-cp39-cp39-win32.whl", hash = "sha256:6b418afe5df18233fc6b6093deb82a32895b6bb0b1155c2cdb05203f583053f1"},
+    {file = "lxml-4.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:f1496ea22ca2c830cbcbd473de8f114a320da308438ae65abad6bab7867fe38f"},
+    {file = "lxml-4.9.2-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:b264171e3143d842ded311b7dccd46ff9ef34247129ff5bf5066123c55c2431c"},
+    {file = "lxml-4.9.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:0dc313ef231edf866912e9d8f5a042ddab56c752619e92dfd3a2c277e6a7299a"},
+    {file = "lxml-4.9.2-pp38-pypy38_pp73-macosx_10_15_x86_64.whl", hash = "sha256:16efd54337136e8cd72fb9485c368d91d77a47ee2d42b057564aae201257d419"},
+    {file = "lxml-4.9.2-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:0f2b1e0d79180f344ff9f321327b005ca043a50ece8713de61d1cb383fb8ac05"},
+    {file = "lxml-4.9.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:7b770ed79542ed52c519119473898198761d78beb24b107acf3ad65deae61f1f"},
+    {file = "lxml-4.9.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:efa29c2fe6b4fdd32e8ef81c1528506895eca86e1d8c4657fda04c9b3786ddf9"},
+    {file = "lxml-4.9.2-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7e91ee82f4199af8c43d8158024cbdff3d931df350252288f0d4ce656df7f3b5"},
+    {file = "lxml-4.9.2-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:b23e19989c355ca854276178a0463951a653309fb8e57ce674497f2d9f208746"},
+    {file = "lxml-4.9.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:01d36c05f4afb8f7c20fd9ed5badca32a2029b93b1750f571ccc0b142531caf7"},
+    {file = "lxml-4.9.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7b515674acfdcadb0eb5d00d8a709868173acece5cb0be3dd165950cbfdf5409"},
+    {file = "lxml-4.9.2.tar.gz", hash = "sha256:2455cfaeb7ac70338b3257f41e21f0724f4b5b0c0e7702da67ee6c3640835b67"},
+]
+
+[package.extras]
+cssselect = ["cssselect (>=0.7)"]
+html5 = ["html5lib"]
+htmlsoup = ["BeautifulSoup4"]
+source = ["Cython (>=0.29.7)"]
+
+[[package]]
+name = "markupsafe"
+version = "2.0.1"
+description = "Safely add untrusted strings to HTML/XML markup."
+optional = false
+python-versions = ">=3.6"
+files = [
+    {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53"},
+    {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38"},
+    {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad"},
+    {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d"},
+    {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646"},
+    {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4dc8f9fb58f7364b63fd9f85013b780ef83c11857ae79f2feda41e270468dd9b"},
+    {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:20dca64a3ef2d6e4d5d615a3fd418ad3bde77a47ec8a23d984a12b5b4c74491a"},
+    {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cdfba22ea2f0029c9261a4bd07e830a8da012291fbe44dc794e488b6c9bb353a"},
+    {file = "MarkupSafe-2.0.1-cp310-cp310-win32.whl", hash = "sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28"},
+    {file = "MarkupSafe-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134"},
+    {file = "MarkupSafe-2.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51"},
+    {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff"},
+    {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b"},
+    {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94"},
+    {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"},
+    {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f"},
+    {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c"},
+    {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724"},
+    {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145"},
+    {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:deb993cacb280823246a026e3b2d81c493c53de6acfd5e6bfe31ab3402bb37dd"},
+    {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:63f3268ba69ace99cab4e3e3b5840b03340efed0948ab8f78d2fd87ee5442a4f"},
+    {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:8d206346619592c6200148b01a2142798c989edcb9c896f9ac9722a99d4e77e6"},
+    {file = "MarkupSafe-2.0.1-cp36-cp36m-win32.whl", hash = "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d"},
+    {file = "MarkupSafe-2.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9"},
+    {file = "MarkupSafe-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567"},
+    {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18"},
+    {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f"},
+    {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f"},
+    {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2"},
+    {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d"},
+    {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85"},
+    {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6"},
+    {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864"},
+    {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d6c7ebd4e944c85e2c3421e612a7057a2f48d478d79e61800d81468a8d842207"},
+    {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f0567c4dc99f264f49fe27da5f735f414c4e7e7dd850cfd8e69f0862d7c74ea9"},
+    {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:89c687013cb1cd489a0f0ac24febe8c7a666e6e221b783e53ac50ebf68e45d86"},
+    {file = "MarkupSafe-2.0.1-cp37-cp37m-win32.whl", hash = "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415"},
+    {file = "MarkupSafe-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914"},
+    {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9"},
+    {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066"},
+    {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35"},
+    {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b"},
+    {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298"},
+    {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75"},
+    {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb"},
+    {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b"},
+    {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a"},
+    {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6"},
+    {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:aca6377c0cb8a8253e493c6b451565ac77e98c2951c45f913e0b52facdcff83f"},
+    {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:04635854b943835a6ea959e948d19dcd311762c5c0c6e1f0e16ee57022669194"},
+    {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6300b8454aa6930a24b9618fbb54b5a68135092bc666f7b06901f897fa5c2fee"},
+    {file = "MarkupSafe-2.0.1-cp38-cp38-win32.whl", hash = "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64"},
+    {file = "MarkupSafe-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833"},
+    {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26"},
+    {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7"},
+    {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8"},
+    {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5"},
+    {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135"},
+    {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902"},
+    {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509"},
+    {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1"},
+    {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac"},
+    {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6"},
+    {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4296f2b1ce8c86a6aea78613c34bb1a672ea0e3de9c6ba08a960efe0b0a09047"},
+    {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f02365d4e99430a12647f09b6cc8bab61a6564363f313126f775eb4f6ef798e"},
+    {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5b6d930f030f8ed98e3e6c98ffa0652bdb82601e7a016ec2ab5d7ff23baa78d1"},
+    {file = "MarkupSafe-2.0.1-cp39-cp39-win32.whl", hash = "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74"},
+    {file = "MarkupSafe-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8"},
+    {file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"},
+]
+
+[[package]]
+name = "matplotlib"
+version = "3.7.1"
+description = "Python plotting package"
+optional = false
+python-versions = ">=3.8"
+files = [
+    {file = "matplotlib-3.7.1-cp310-cp310-macosx_10_12_universal2.whl", hash = "sha256:95cbc13c1fc6844ab8812a525bbc237fa1470863ff3dace7352e910519e194b1"},
+    {file = "matplotlib-3.7.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:08308bae9e91aca1ec6fd6dda66237eef9f6294ddb17f0d0b3c863169bf82353"},
+    {file = "matplotlib-3.7.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:544764ba51900da4639c0f983b323d288f94f65f4024dc40ecb1542d74dc0500"},
+    {file = "matplotlib-3.7.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56d94989191de3fcc4e002f93f7f1be5da476385dde410ddafbb70686acf00ea"},
+    {file = "matplotlib-3.7.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e99bc9e65901bb9a7ce5e7bb24af03675cbd7c70b30ac670aa263240635999a4"},
+    {file = "matplotlib-3.7.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb7d248c34a341cd4c31a06fd34d64306624c8cd8d0def7abb08792a5abfd556"},
+    {file = "matplotlib-3.7.1-cp310-cp310-win32.whl", hash = "sha256:ce463ce590f3825b52e9fe5c19a3c6a69fd7675a39d589e8b5fbe772272b3a24"},
+    {file = "matplotlib-3.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:3d7bc90727351fb841e4d8ae620d2d86d8ed92b50473cd2b42ce9186104ecbba"},
+    {file = "matplotlib-3.7.1-cp311-cp311-macosx_10_12_universal2.whl", hash = "sha256:770a205966d641627fd5cf9d3cb4b6280a716522cd36b8b284a8eb1581310f61"},
+    {file = "matplotlib-3.7.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:f67bfdb83a8232cb7a92b869f9355d677bce24485c460b19d01970b64b2ed476"},
+    {file = "matplotlib-3.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2bf092f9210e105f414a043b92af583c98f50050559616930d884387d0772aba"},
+    {file = "matplotlib-3.7.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89768d84187f31717349c6bfadc0e0d8c321e8eb34522acec8a67b1236a66332"},
+    {file = "matplotlib-3.7.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83111e6388dec67822e2534e13b243cc644c7494a4bb60584edbff91585a83c6"},
+    {file = "matplotlib-3.7.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a867bf73a7eb808ef2afbca03bcdb785dae09595fbe550e1bab0cd023eba3de0"},
+    {file = "matplotlib-3.7.1-cp311-cp311-win32.whl", hash = "sha256:fbdeeb58c0cf0595efe89c05c224e0a502d1aa6a8696e68a73c3efc6bc354304"},
+    {file = "matplotlib-3.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:c0bd19c72ae53e6ab979f0ac6a3fafceb02d2ecafa023c5cca47acd934d10be7"},
+    {file = "matplotlib-3.7.1-cp38-cp38-macosx_10_12_universal2.whl", hash = "sha256:6eb88d87cb2c49af00d3bbc33a003f89fd9f78d318848da029383bfc08ecfbfb"},
+    {file = "matplotlib-3.7.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:cf0e4f727534b7b1457898c4f4ae838af1ef87c359b76dcd5330fa31893a3ac7"},
+    {file = "matplotlib-3.7.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:46a561d23b91f30bccfd25429c3c706afe7d73a5cc64ef2dfaf2b2ac47c1a5dc"},
+    {file = "matplotlib-3.7.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8704726d33e9aa8a6d5215044b8d00804561971163563e6e6591f9dcf64340cc"},
+    {file = "matplotlib-3.7.1-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4cf327e98ecf08fcbb82685acaf1939d3338548620ab8dfa02828706402c34de"},
+    {file = "matplotlib-3.7.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:617f14ae9d53292ece33f45cba8503494ee199a75b44de7717964f70637a36aa"},
+    {file = "matplotlib-3.7.1-cp38-cp38-win32.whl", hash = "sha256:7c9a4b2da6fac77bcc41b1ea95fadb314e92508bf5493ceff058e727e7ecf5b0"},
+    {file = "matplotlib-3.7.1-cp38-cp38-win_amd64.whl", hash = "sha256:14645aad967684e92fc349493fa10c08a6da514b3d03a5931a1bac26e6792bd1"},
+    {file = "matplotlib-3.7.1-cp39-cp39-macosx_10_12_universal2.whl", hash = "sha256:81a6b377ea444336538638d31fdb39af6be1a043ca5e343fe18d0f17e098770b"},
+    {file = "matplotlib-3.7.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:28506a03bd7f3fe59cd3cd4ceb2a8d8a2b1db41afede01f66c42561b9be7b4b7"},
+    {file = "matplotlib-3.7.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8c587963b85ce41e0a8af53b9b2de8dddbf5ece4c34553f7bd9d066148dc719c"},
+    {file = "matplotlib-3.7.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8bf26ade3ff0f27668989d98c8435ce9327d24cffb7f07d24ef609e33d582439"},
+    {file = "matplotlib-3.7.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:def58098f96a05f90af7e92fd127d21a287068202aa43b2a93476170ebd99e87"},
+    {file = "matplotlib-3.7.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f883a22a56a84dba3b588696a2b8a1ab0d2c3d41be53264115c71b0a942d8fdb"},
+    {file = "matplotlib-3.7.1-cp39-cp39-win32.whl", hash = "sha256:4f99e1b234c30c1e9714610eb0c6d2f11809c9c78c984a613ae539ea2ad2eb4b"},
+    {file = "matplotlib-3.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:3ba2af245e36990facf67fde840a760128ddd71210b2ab6406e640188d69d136"},
+    {file = "matplotlib-3.7.1-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3032884084f541163f295db8a6536e0abb0db464008fadca6c98aaf84ccf4717"},
+    {file = "matplotlib-3.7.1-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3a2cb34336110e0ed8bb4f650e817eed61fa064acbefeb3591f1b33e3a84fd96"},
+    {file = "matplotlib-3.7.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b867e2f952ed592237a1828f027d332d8ee219ad722345b79a001f49df0936eb"},
+    {file = "matplotlib-3.7.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:57bfb8c8ea253be947ccb2bc2d1bb3862c2bccc662ad1b4626e1f5e004557042"},
+    {file = "matplotlib-3.7.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:438196cdf5dc8d39b50a45cb6e3f6274edbcf2254f85fa9b895bf85851c3a613"},
+    {file = "matplotlib-3.7.1-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:21e9cff1a58d42e74d01153360de92b326708fb205250150018a52c70f43c290"},
+    {file = "matplotlib-3.7.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75d4725d70b7c03e082bbb8a34639ede17f333d7247f56caceb3801cb6ff703d"},
+    {file = "matplotlib-3.7.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:97cc368a7268141afb5690760921765ed34867ffb9655dd325ed207af85c7529"},
+    {file = "matplotlib-3.7.1.tar.gz", hash = "sha256:7b73305f25eab4541bd7ee0b96d87e53ae9c9f1823be5659b806cd85786fe882"},
+]
+
+[package.dependencies]
+contourpy = ">=1.0.1"
+cycler = ">=0.10"
+fonttools = ">=4.22.0"
+importlib-resources = {version = ">=3.2.0", markers = "python_version < \"3.10\""}
+kiwisolver = ">=1.0.1"
+numpy = ">=1.20"
+packaging = ">=20.0"
+pillow = ">=6.2.0"
+pyparsing = ">=2.3.1"
+python-dateutil = ">=2.7"
+
+[[package]]
+name = "multidict"
+version = "6.0.4"
+description = "multidict implementation"
+optional = false
+python-versions = ">=3.7"
+files = [
+    {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b1a97283e0c85772d613878028fec909f003993e1007eafa715b24b377cb9b8"},
+    {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eeb6dcc05e911516ae3d1f207d4b0520d07f54484c49dfc294d6e7d63b734171"},
+    {file = "multidict-6.0.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d6d635d5209b82a3492508cf5b365f3446afb65ae7ebd755e70e18f287b0adf7"},
+    {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c048099e4c9e9d615545e2001d3d8a4380bd403e1a0578734e0d31703d1b0c0b"},
+    {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ea20853c6dbbb53ed34cb4d080382169b6f4554d394015f1bef35e881bf83547"},
+    {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16d232d4e5396c2efbbf4f6d4df89bfa905eb0d4dc5b3549d872ab898451f569"},
+    {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36c63aaa167f6c6b04ef2c85704e93af16c11d20de1d133e39de6a0e84582a93"},
+    {file = "multidict-6.0.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:64bdf1086b6043bf519869678f5f2757f473dee970d7abf6da91ec00acb9cb98"},
+    {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:43644e38f42e3af682690876cff722d301ac585c5b9e1eacc013b7a3f7b696a0"},
+    {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7582a1d1030e15422262de9f58711774e02fa80df0d1578995c76214f6954988"},
+    {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ddff9c4e225a63a5afab9dd15590432c22e8057e1a9a13d28ed128ecf047bbdc"},
+    {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ee2a1ece51b9b9e7752e742cfb661d2a29e7bcdba2d27e66e28a99f1890e4fa0"},
+    {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a2e4369eb3d47d2034032a26c7a80fcb21a2cb22e1173d761a162f11e562caa5"},
+    {file = "multidict-6.0.4-cp310-cp310-win32.whl", hash = "sha256:574b7eae1ab267e5f8285f0fe881f17efe4b98c39a40858247720935b893bba8"},
+    {file = "multidict-6.0.4-cp310-cp310-win_amd64.whl", hash = "sha256:4dcbb0906e38440fa3e325df2359ac6cb043df8e58c965bb45f4e406ecb162cc"},
+    {file = "multidict-6.0.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0dfad7a5a1e39c53ed00d2dd0c2e36aed4650936dc18fd9a1826a5ae1cad6f03"},
+    {file = "multidict-6.0.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:64da238a09d6039e3bd39bb3aee9c21a5e34f28bfa5aa22518581f910ff94af3"},
+    {file = "multidict-6.0.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ff959bee35038c4624250473988b24f846cbeb2c6639de3602c073f10410ceba"},
+    {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:01a3a55bd90018c9c080fbb0b9f4891db37d148a0a18722b42f94694f8b6d4c9"},
+    {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c5cb09abb18c1ea940fb99360ea0396f34d46566f157122c92dfa069d3e0e982"},
+    {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:666daae833559deb2d609afa4490b85830ab0dfca811a98b70a205621a6109fe"},
+    {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11bdf3f5e1518b24530b8241529d2050014c884cf18b6fc69c0c2b30ca248710"},
+    {file = "multidict-6.0.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d18748f2d30f94f498e852c67d61261c643b349b9d2a581131725595c45ec6c"},
+    {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:458f37be2d9e4c95e2d8866a851663cbc76e865b78395090786f6cd9b3bbf4f4"},
+    {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b1a2eeedcead3a41694130495593a559a668f382eee0727352b9a41e1c45759a"},
+    {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7d6ae9d593ef8641544d6263c7fa6408cc90370c8cb2bbb65f8d43e5b0351d9c"},
+    {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:5979b5632c3e3534e42ca6ff856bb24b2e3071b37861c2c727ce220d80eee9ed"},
+    {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dcfe792765fab89c365123c81046ad4103fcabbc4f56d1c1997e6715e8015461"},
+    {file = "multidict-6.0.4-cp311-cp311-win32.whl", hash = "sha256:3601a3cece3819534b11d4efc1eb76047488fddd0c85a3948099d5da4d504636"},
+    {file = "multidict-6.0.4-cp311-cp311-win_amd64.whl", hash = "sha256:81a4f0b34bd92df3da93315c6a59034df95866014ac08535fc819f043bfd51f0"},
+    {file = "multidict-6.0.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:67040058f37a2a51ed8ea8f6b0e6ee5bd78ca67f169ce6122f3e2ec80dfe9b78"},
+    {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:853888594621e6604c978ce2a0444a1e6e70c8d253ab65ba11657659dcc9100f"},
+    {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:39ff62e7d0f26c248b15e364517a72932a611a9b75f35b45be078d81bdb86603"},
+    {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:af048912e045a2dc732847d33821a9d84ba553f5c5f028adbd364dd4765092ac"},
+    {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1e8b901e607795ec06c9e42530788c45ac21ef3aaa11dbd0c69de543bfb79a9"},
+    {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62501642008a8b9871ddfccbf83e4222cf8ac0d5aeedf73da36153ef2ec222d2"},
+    {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:99b76c052e9f1bc0721f7541e5e8c05db3941eb9ebe7b8553c625ef88d6eefde"},
+    {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:509eac6cf09c794aa27bcacfd4d62c885cce62bef7b2c3e8b2e49d365b5003fe"},
+    {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:21a12c4eb6ddc9952c415f24eef97e3e55ba3af61f67c7bc388dcdec1404a067"},
+    {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:5cad9430ab3e2e4fa4a2ef4450f548768400a2ac635841bc2a56a2052cdbeb87"},
+    {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ab55edc2e84460694295f401215f4a58597f8f7c9466faec545093045476327d"},
+    {file = "multidict-6.0.4-cp37-cp37m-win32.whl", hash = "sha256:5a4dcf02b908c3b8b17a45fb0f15b695bf117a67b76b7ad18b73cf8e92608775"},
+    {file = "multidict-6.0.4-cp37-cp37m-win_amd64.whl", hash = "sha256:6ed5f161328b7df384d71b07317f4d8656434e34591f20552c7bcef27b0ab88e"},
+    {file = "multidict-6.0.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5fc1b16f586f049820c5c5b17bb4ee7583092fa0d1c4e28b5239181ff9532e0c"},
+    {file = "multidict-6.0.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1502e24330eb681bdaa3eb70d6358e818e8e8f908a22a1851dfd4e15bc2f8161"},
+    {file = "multidict-6.0.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b692f419760c0e65d060959df05f2a531945af31fda0c8a3b3195d4efd06de11"},
+    {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45e1ecb0379bfaab5eef059f50115b54571acfbe422a14f668fc8c27ba410e7e"},
+    {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ddd3915998d93fbcd2566ddf9cf62cdb35c9e093075f862935573d265cf8f65d"},
+    {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:59d43b61c59d82f2effb39a93c48b845efe23a3852d201ed2d24ba830d0b4cf2"},
+    {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc8e1d0c705233c5dd0c5e6460fbad7827d5d36f310a0fadfd45cc3029762258"},
+    {file = "multidict-6.0.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6aa0418fcc838522256761b3415822626f866758ee0bc6632c9486b179d0b52"},
+    {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6748717bb10339c4760c1e63da040f5f29f5ed6e59d76daee30305894069a660"},
+    {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4d1a3d7ef5e96b1c9e92f973e43aa5e5b96c659c9bc3124acbbd81b0b9c8a951"},
+    {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4372381634485bec7e46718edc71528024fcdc6f835baefe517b34a33c731d60"},
+    {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:fc35cb4676846ef752816d5be2193a1e8367b4c1397b74a565a9d0389c433a1d"},
+    {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4b9d9e4e2b37daddb5c23ea33a3417901fa7c7b3dee2d855f63ee67a0b21e5b1"},
+    {file = "multidict-6.0.4-cp38-cp38-win32.whl", hash = "sha256:e41b7e2b59679edfa309e8db64fdf22399eec4b0b24694e1b2104fb789207779"},
+    {file = "multidict-6.0.4-cp38-cp38-win_amd64.whl", hash = "sha256:d6c254ba6e45d8e72739281ebc46ea5eb5f101234f3ce171f0e9f5cc86991480"},
+    {file = "multidict-6.0.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:16ab77bbeb596e14212e7bab8429f24c1579234a3a462105cda4a66904998664"},
+    {file = "multidict-6.0.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc779e9e6f7fda81b3f9aa58e3a6091d49ad528b11ed19f6621408806204ad35"},
+    {file = "multidict-6.0.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ceef517eca3e03c1cceb22030a3e39cb399ac86bff4e426d4fc6ae49052cc60"},
+    {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:281af09f488903fde97923c7744bb001a9b23b039a909460d0f14edc7bf59706"},
+    {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:52f2dffc8acaba9a2f27174c41c9e57f60b907bb9f096b36b1a1f3be71c6284d"},
+    {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b41156839806aecb3641f3208c0dafd3ac7775b9c4c422d82ee2a45c34ba81ca"},
+    {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5e3fc56f88cc98ef8139255cf8cd63eb2c586531e43310ff859d6bb3a6b51f1"},
+    {file = "multidict-6.0.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8316a77808c501004802f9beebde51c9f857054a0c871bd6da8280e718444449"},
+    {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f70b98cd94886b49d91170ef23ec5c0e8ebb6f242d734ed7ed677b24d50c82cf"},
+    {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bf6774e60d67a9efe02b3616fee22441d86fab4c6d335f9d2051d19d90a40063"},
+    {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:e69924bfcdda39b722ef4d9aa762b2dd38e4632b3641b1d9a57ca9cd18f2f83a"},
+    {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:6b181d8c23da913d4ff585afd1155a0e1194c0b50c54fcfe286f70cdaf2b7176"},
+    {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:52509b5be062d9eafc8170e53026fbc54cf3b32759a23d07fd935fb04fc22d95"},
+    {file = "multidict-6.0.4-cp39-cp39-win32.whl", hash = "sha256:27c523fbfbdfd19c6867af7346332b62b586eed663887392cff78d614f9ec313"},
+    {file = "multidict-6.0.4-cp39-cp39-win_amd64.whl", hash = "sha256:33029f5734336aa0d4c0384525da0387ef89148dc7191aae00ca5fb23d7aafc2"},
+    {file = "multidict-6.0.4.tar.gz", hash = "sha256:3666906492efb76453c0e7b97f2cf459b0682e7402c0489a95484965dbc1da49"},
+]
+
+[[package]]
+name = "mypy-extensions"
+version = "1.0.0"
+description = "Type system extensions for programs checked with the mypy type checker."
+optional = false
+python-versions = ">=3.5"
+files = [
+    {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"},
+    {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"},
+]
+
+[[package]]
+name = "numpy"
+version = "1.24.3"
+description = "Fundamental package for array computing in Python"
+optional = false
+python-versions = ">=3.8"
+files = [
+    {file = "numpy-1.24.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3c1104d3c036fb81ab923f507536daedc718d0ad5a8707c6061cdfd6d184e570"},
+    {file = "numpy-1.24.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:202de8f38fc4a45a3eea4b63e2f376e5f2dc64ef0fa692838e31a808520efaf7"},
+    {file = "numpy-1.24.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8535303847b89aa6b0f00aa1dc62867b5a32923e4d1681a35b5eef2d9591a463"},
+    {file = "numpy-1.24.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d926b52ba1367f9acb76b0df6ed21f0b16a1ad87c6720a1121674e5cf63e2b6"},
+    {file = "numpy-1.24.3-cp310-cp310-win32.whl", hash = "sha256:f21c442fdd2805e91799fbe044a7b999b8571bb0ab0f7850d0cb9641a687092b"},
+    {file = "numpy-1.24.3-cp310-cp310-win_amd64.whl", hash = "sha256:ab5f23af8c16022663a652d3b25dcdc272ac3f83c3af4c02eb8b824e6b3ab9d7"},
+    {file = "numpy-1.24.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9a7721ec204d3a237225db3e194c25268faf92e19338a35f3a224469cb6039a3"},
+    {file = "numpy-1.24.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d6cc757de514c00b24ae8cf5c876af2a7c3df189028d68c0cb4eaa9cd5afc2bf"},
+    {file = "numpy-1.24.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76e3f4e85fc5d4fd311f6e9b794d0c00e7002ec122be271f2019d63376f1d385"},
+    {file = "numpy-1.24.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1d3c026f57ceaad42f8231305d4653d5f05dc6332a730ae5c0bea3513de0950"},
+    {file = "numpy-1.24.3-cp311-cp311-win32.whl", hash = "sha256:c91c4afd8abc3908e00a44b2672718905b8611503f7ff87390cc0ac3423fb096"},
+    {file = "numpy-1.24.3-cp311-cp311-win_amd64.whl", hash = "sha256:5342cf6aad47943286afa6f1609cad9b4266a05e7f2ec408e2cf7aea7ff69d80"},
+    {file = "numpy-1.24.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7776ea65423ca6a15255ba1872d82d207bd1e09f6d0894ee4a64678dd2204078"},
+    {file = "numpy-1.24.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ae8d0be48d1b6ed82588934aaaa179875e7dc4f3d84da18d7eae6eb3f06c242c"},
+    {file = "numpy-1.24.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ecde0f8adef7dfdec993fd54b0f78183051b6580f606111a6d789cd14c61ea0c"},
+    {file = "numpy-1.24.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4749e053a29364d3452c034827102ee100986903263e89884922ef01a0a6fd2f"},
+    {file = "numpy-1.24.3-cp38-cp38-win32.whl", hash = "sha256:d933fabd8f6a319e8530d0de4fcc2e6a61917e0b0c271fded460032db42a0fe4"},
+    {file = "numpy-1.24.3-cp38-cp38-win_amd64.whl", hash = "sha256:56e48aec79ae238f6e4395886b5eaed058abb7231fb3361ddd7bfdf4eed54289"},
+    {file = "numpy-1.24.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4719d5aefb5189f50887773699eaf94e7d1e02bf36c1a9d353d9f46703758ca4"},
+    {file = "numpy-1.24.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0ec87a7084caa559c36e0a2309e4ecb1baa03b687201d0a847c8b0ed476a7187"},
+    {file = "numpy-1.24.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea8282b9bcfe2b5e7d491d0bf7f3e2da29700cec05b49e64d6246923329f2b02"},
+    {file = "numpy-1.24.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:210461d87fb02a84ef243cac5e814aad2b7f4be953b32cb53327bb49fd77fbb4"},
+    {file = "numpy-1.24.3-cp39-cp39-win32.whl", hash = "sha256:784c6da1a07818491b0ffd63c6bbe5a33deaa0e25a20e1b3ea20cf0e43f8046c"},
+    {file = "numpy-1.24.3-cp39-cp39-win_amd64.whl", hash = "sha256:d5036197ecae68d7f491fcdb4df90082b0d4960ca6599ba2659957aafced7c17"},
+    {file = "numpy-1.24.3-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:352ee00c7f8387b44d19f4cada524586f07379c0d49270f87233983bc5087ca0"},
+    {file = "numpy-1.24.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a7d6acc2e7524c9955e5c903160aa4ea083736fde7e91276b0e5d98e6332812"},
+    {file = "numpy-1.24.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:35400e6a8d102fd07c71ed7dcadd9eb62ee9a6e84ec159bd48c28235bbb0f8e4"},
+    {file = "numpy-1.24.3.tar.gz", hash = "sha256:ab344f1bf21f140adab8e47fdbc7c35a477dc01408791f8ba00d018dd0bc5155"},
+]
+
+[[package]]
+name = "packaging"
+version = "23.1"
+description = "Core utilities for Python packages"
+optional = false
+python-versions = ">=3.7"
+files = [
+    {file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"},
+    {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"},
+]
+
+[[package]]
+name = "pillow"
+version = "9.5.0"
+description = "Python Imaging Library (Fork)"
+optional = false
+python-versions = ">=3.7"
+files = [
+    {file = "Pillow-9.5.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:ace6ca218308447b9077c14ea4ef381ba0b67ee78d64046b3f19cf4e1139ad16"},
+    {file = "Pillow-9.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d3d403753c9d5adc04d4694d35cf0391f0f3d57c8e0030aac09d7678fa8030aa"},
+    {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ba1b81ee69573fe7124881762bb4cd2e4b6ed9dd28c9c60a632902fe8db8b38"},
+    {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe7e1c262d3392afcf5071df9afa574544f28eac825284596ac6db56e6d11062"},
+    {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f36397bf3f7d7c6a3abdea815ecf6fd14e7fcd4418ab24bae01008d8d8ca15e"},
+    {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:252a03f1bdddce077eff2354c3861bf437c892fb1832f75ce813ee94347aa9b5"},
+    {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:85ec677246533e27770b0de5cf0f9d6e4ec0c212a1f89dfc941b64b21226009d"},
+    {file = "Pillow-9.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b416f03d37d27290cb93597335a2f85ed446731200705b22bb927405320de903"},
+    {file = "Pillow-9.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1781a624c229cb35a2ac31cc4a77e28cafc8900733a864870c49bfeedacd106a"},
+    {file = "Pillow-9.5.0-cp310-cp310-win32.whl", hash = "sha256:8507eda3cd0608a1f94f58c64817e83ec12fa93a9436938b191b80d9e4c0fc44"},
+    {file = "Pillow-9.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:d3c6b54e304c60c4181da1c9dadf83e4a54fd266a99c70ba646a9baa626819eb"},
+    {file = "Pillow-9.5.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:7ec6f6ce99dab90b52da21cf0dc519e21095e332ff3b399a357c187b1a5eee32"},
+    {file = "Pillow-9.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:560737e70cb9c6255d6dcba3de6578a9e2ec4b573659943a5e7e4af13f298f5c"},
+    {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:96e88745a55b88a7c64fa49bceff363a1a27d9a64e04019c2281049444a571e3"},
+    {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d9c206c29b46cfd343ea7cdfe1232443072bbb270d6a46f59c259460db76779a"},
+    {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cfcc2c53c06f2ccb8976fb5c71d448bdd0a07d26d8e07e321c103416444c7ad1"},
+    {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:a0f9bb6c80e6efcde93ffc51256d5cfb2155ff8f78292f074f60f9e70b942d99"},
+    {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:8d935f924bbab8f0a9a28404422da8af4904e36d5c33fc6f677e4c4485515625"},
+    {file = "Pillow-9.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fed1e1cf6a42577953abbe8e6cf2fe2f566daebde7c34724ec8803c4c0cda579"},
+    {file = "Pillow-9.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c1170d6b195555644f0616fd6ed929dfcf6333b8675fcca044ae5ab110ded296"},
+    {file = "Pillow-9.5.0-cp311-cp311-win32.whl", hash = "sha256:54f7102ad31a3de5666827526e248c3530b3a33539dbda27c6843d19d72644ec"},
+    {file = "Pillow-9.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:cfa4561277f677ecf651e2b22dc43e8f5368b74a25a8f7d1d4a3a243e573f2d4"},
+    {file = "Pillow-9.5.0-cp311-cp311-win_arm64.whl", hash = "sha256:965e4a05ef364e7b973dd17fc765f42233415974d773e82144c9bbaaaea5d089"},
+    {file = "Pillow-9.5.0-cp312-cp312-win32.whl", hash = "sha256:22baf0c3cf0c7f26e82d6e1adf118027afb325e703922c8dfc1d5d0156bb2eeb"},
+    {file = "Pillow-9.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:432b975c009cf649420615388561c0ce7cc31ce9b2e374db659ee4f7d57a1f8b"},
+    {file = "Pillow-9.5.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:5d4ebf8e1db4441a55c509c4baa7a0587a0210f7cd25fcfe74dbbce7a4bd1906"},
+    {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:375f6e5ee9620a271acb6820b3d1e94ffa8e741c0601db4c0c4d3cb0a9c224bf"},
+    {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99eb6cafb6ba90e436684e08dad8be1637efb71c4f2180ee6b8f940739406e78"},
+    {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dfaaf10b6172697b9bceb9a3bd7b951819d1ca339a5ef294d1f1ac6d7f63270"},
+    {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:763782b2e03e45e2c77d7779875f4432e25121ef002a41829d8868700d119392"},
+    {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:35f6e77122a0c0762268216315bf239cf52b88865bba522999dc38f1c52b9b47"},
+    {file = "Pillow-9.5.0-cp37-cp37m-win32.whl", hash = "sha256:aca1c196f407ec7cf04dcbb15d19a43c507a81f7ffc45b690899d6a76ac9fda7"},
+    {file = "Pillow-9.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:322724c0032af6692456cd6ed554bb85f8149214d97398bb80613b04e33769f6"},
+    {file = "Pillow-9.5.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:a0aa9417994d91301056f3d0038af1199eb7adc86e646a36b9e050b06f526597"},
+    {file = "Pillow-9.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f8286396b351785801a976b1e85ea88e937712ee2c3ac653710a4a57a8da5d9c"},
+    {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c830a02caeb789633863b466b9de10c015bded434deb3ec87c768e53752ad22a"},
+    {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fbd359831c1657d69bb81f0db962905ee05e5e9451913b18b831febfe0519082"},
+    {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8fc330c3370a81bbf3f88557097d1ea26cd8b019d6433aa59f71195f5ddebbf"},
+    {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:7002d0797a3e4193c7cdee3198d7c14f92c0836d6b4a3f3046a64bd1ce8df2bf"},
+    {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:229e2c79c00e85989a34b5981a2b67aa079fd08c903f0aaead522a1d68d79e51"},
+    {file = "Pillow-9.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9adf58f5d64e474bed00d69bcd86ec4bcaa4123bfa70a65ce72e424bfb88ed96"},
+    {file = "Pillow-9.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:662da1f3f89a302cc22faa9f14a262c2e3951f9dbc9617609a47521c69dd9f8f"},
+    {file = "Pillow-9.5.0-cp38-cp38-win32.whl", hash = "sha256:6608ff3bf781eee0cd14d0901a2b9cc3d3834516532e3bd673a0a204dc8615fc"},
+    {file = "Pillow-9.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:e49eb4e95ff6fd7c0c402508894b1ef0e01b99a44320ba7d8ecbabefddcc5569"},
+    {file = "Pillow-9.5.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:482877592e927fd263028c105b36272398e3e1be3269efda09f6ba21fd83ec66"},
+    {file = "Pillow-9.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3ded42b9ad70e5f1754fb7c2e2d6465a9c842e41d178f262e08b8c85ed8a1d8e"},
+    {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c446d2245ba29820d405315083d55299a796695d747efceb5717a8b450324115"},
+    {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8aca1152d93dcc27dc55395604dcfc55bed5f25ef4c98716a928bacba90d33a3"},
+    {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:608488bdcbdb4ba7837461442b90ea6f3079397ddc968c31265c1e056964f1ef"},
+    {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:60037a8db8750e474af7ffc9faa9b5859e6c6d0a50e55c45576bf28be7419705"},
+    {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:07999f5834bdc404c442146942a2ecadd1cb6292f5229f4ed3b31e0a108746b1"},
+    {file = "Pillow-9.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a127ae76092974abfbfa38ca2d12cbeddcdeac0fb71f9627cc1135bedaf9d51a"},
+    {file = "Pillow-9.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:489f8389261e5ed43ac8ff7b453162af39c3e8abd730af8363587ba64bb2e865"},
+    {file = "Pillow-9.5.0-cp39-cp39-win32.whl", hash = "sha256:9b1af95c3a967bf1da94f253e56b6286b50af23392a886720f563c547e48e964"},
+    {file = "Pillow-9.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:77165c4a5e7d5a284f10a6efaa39a0ae8ba839da344f20b111d62cc932fa4e5d"},
+    {file = "Pillow-9.5.0-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:833b86a98e0ede388fa29363159c9b1a294b0905b5128baf01db683672f230f5"},
+    {file = "Pillow-9.5.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aaf305d6d40bd9632198c766fb64f0c1a83ca5b667f16c1e79e1661ab5060140"},
+    {file = "Pillow-9.5.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0852ddb76d85f127c135b6dd1f0bb88dbb9ee990d2cd9aa9e28526c93e794fba"},
+    {file = "Pillow-9.5.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:91ec6fe47b5eb5a9968c79ad9ed78c342b1f97a091677ba0e012701add857829"},
+    {file = "Pillow-9.5.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:cb841572862f629b99725ebaec3287fc6d275be9b14443ea746c1dd325053cbd"},
+    {file = "Pillow-9.5.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:c380b27d041209b849ed246b111b7c166ba36d7933ec6e41175fd15ab9eb1572"},
+    {file = "Pillow-9.5.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c9af5a3b406a50e313467e3565fc99929717f780164fe6fbb7704edba0cebbe"},
+    {file = "Pillow-9.5.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5671583eab84af046a397d6d0ba25343c00cd50bce03787948e0fff01d4fd9b1"},
+    {file = "Pillow-9.5.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:84a6f19ce086c1bf894644b43cd129702f781ba5751ca8572f08aa40ef0ab7b7"},
+    {file = "Pillow-9.5.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:1e7723bd90ef94eda669a3c2c19d549874dd5badaeefabefd26053304abe5799"},
+    {file = "Pillow-9.5.0.tar.gz", hash = "sha256:bf548479d336726d7a0eceb6e767e179fbde37833ae42794602631a070d630f1"},
+]
+
+[package.extras]
+docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-removed-in", "sphinxext-opengraph"]
+tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"]
+
+[[package]]
+name = "playwright"
+version = "1.34.0"
+description = "A high-level API to automate web browsers"
+optional = false
+python-versions = ">=3.7"
+files = [
+    {file = "playwright-1.34.0-py3-none-macosx_10_13_x86_64.whl", hash = "sha256:69bb9b3296e366a23a99277b4c7673cb54ce71a3f5d630f114f7701b61f98f25"},
+    {file = "playwright-1.34.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:402d946631c8458436e099d7731bbf54cf79c9e62e3acae0ea8421e72616926b"},
+    {file = "playwright-1.34.0-py3-none-macosx_11_0_universal2.whl", hash = "sha256:462251cda0fcbb273497d357dbe14b11e43ebceb0bac9b892beda041ff209aa9"},
+    {file = "playwright-1.34.0-py3-none-manylinux1_x86_64.whl", hash = "sha256:a8ba124ea302596a03a66993cd500484fb255cbc10fe0757fa4d49f974267a80"},
+    {file = "playwright-1.34.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf0cb6aac49d24335fe361868aea72b11f276a95e7809f1a5d1c69b4120c46ac"},
+    {file = "playwright-1.34.0-py3-none-win32.whl", hash = "sha256:c50fef189d87243cc09ae0feb8e417fbe434359ccbcc863fb19ba06d46d31c33"},
+    {file = "playwright-1.34.0-py3-none-win_amd64.whl", hash = "sha256:42e16c930e1e910461f4c551a72fc1b900f37124431bf2b6a6d9ddae70042db4"},
+]
+
+[package.dependencies]
+greenlet = "2.0.2"
+pyee = "9.0.4"
+
+[[package]]
+name = "pydantic"
+version = "1.10.8"
+description = "Data validation and settings management using python type hints"
+optional = false
+python-versions = ">=3.7"
+files = [
+    {file = "pydantic-1.10.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1243d28e9b05003a89d72e7915fdb26ffd1d39bdd39b00b7dbe4afae4b557f9d"},
+    {file = "pydantic-1.10.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c0ab53b609c11dfc0c060d94335993cc2b95b2150e25583bec37a49b2d6c6c3f"},
+    {file = "pydantic-1.10.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9613fadad06b4f3bc5db2653ce2f22e0de84a7c6c293909b48f6ed37b83c61f"},
+    {file = "pydantic-1.10.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df7800cb1984d8f6e249351139667a8c50a379009271ee6236138a22a0c0f319"},
+    {file = "pydantic-1.10.8-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:0c6fafa0965b539d7aab0a673a046466d23b86e4b0e8019d25fd53f4df62c277"},
+    {file = "pydantic-1.10.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e82d4566fcd527eae8b244fa952d99f2ca3172b7e97add0b43e2d97ee77f81ab"},
+    {file = "pydantic-1.10.8-cp310-cp310-win_amd64.whl", hash = "sha256:ab523c31e22943713d80d8d342d23b6f6ac4b792a1e54064a8d0cf78fd64e800"},
+    {file = "pydantic-1.10.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:666bdf6066bf6dbc107b30d034615d2627e2121506c555f73f90b54a463d1f33"},
+    {file = "pydantic-1.10.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:35db5301b82e8661fa9c505c800d0990bc14e9f36f98932bb1d248c0ac5cada5"},
+    {file = "pydantic-1.10.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f90c1e29f447557e9e26afb1c4dbf8768a10cc676e3781b6a577841ade126b85"},
+    {file = "pydantic-1.10.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93e766b4a8226e0708ef243e843105bf124e21331694367f95f4e3b4a92bbb3f"},
+    {file = "pydantic-1.10.8-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:88f195f582851e8db960b4a94c3e3ad25692c1c1539e2552f3df7a9e972ef60e"},
+    {file = "pydantic-1.10.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:34d327c81e68a1ecb52fe9c8d50c8a9b3e90d3c8ad991bfc8f953fb477d42fb4"},
+    {file = "pydantic-1.10.8-cp311-cp311-win_amd64.whl", hash = "sha256:d532bf00f381bd6bc62cabc7d1372096b75a33bc197a312b03f5838b4fb84edd"},
+    {file = "pydantic-1.10.8-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7d5b8641c24886d764a74ec541d2fc2c7fb19f6da2a4001e6d580ba4a38f7878"},
+    {file = "pydantic-1.10.8-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b1f6cb446470b7ddf86c2e57cd119a24959af2b01e552f60705910663af09a4"},
+    {file = "pydantic-1.10.8-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c33b60054b2136aef8cf190cd4c52a3daa20b2263917c49adad20eaf381e823b"},
+    {file = "pydantic-1.10.8-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:1952526ba40b220b912cdc43c1c32bcf4a58e3f192fa313ee665916b26befb68"},
+    {file = "pydantic-1.10.8-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:bb14388ec45a7a0dc429e87def6396f9e73c8c77818c927b6a60706603d5f2ea"},
+    {file = "pydantic-1.10.8-cp37-cp37m-win_amd64.whl", hash = "sha256:16f8c3e33af1e9bb16c7a91fc7d5fa9fe27298e9f299cff6cb744d89d573d62c"},
+    {file = "pydantic-1.10.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1ced8375969673929809d7f36ad322934c35de4af3b5e5b09ec967c21f9f7887"},
+    {file = "pydantic-1.10.8-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:93e6bcfccbd831894a6a434b0aeb1947f9e70b7468f274154d03d71fabb1d7c6"},
+    {file = "pydantic-1.10.8-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:191ba419b605f897ede9892f6c56fb182f40a15d309ef0142212200a10af4c18"},
+    {file = "pydantic-1.10.8-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:052d8654cb65174d6f9490cc9b9a200083a82cf5c3c5d3985db765757eb3b375"},
+    {file = "pydantic-1.10.8-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ceb6a23bf1ba4b837d0cfe378329ad3f351b5897c8d4914ce95b85fba96da5a1"},
+    {file = "pydantic-1.10.8-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6f2e754d5566f050954727c77f094e01793bcb5725b663bf628fa6743a5a9108"},
+    {file = "pydantic-1.10.8-cp38-cp38-win_amd64.whl", hash = "sha256:6a82d6cda82258efca32b40040228ecf43a548671cb174a1e81477195ed3ed56"},
+    {file = "pydantic-1.10.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3e59417ba8a17265e632af99cc5f35ec309de5980c440c255ab1ca3ae96a3e0e"},
+    {file = "pydantic-1.10.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:84d80219c3f8d4cad44575e18404099c76851bc924ce5ab1c4c8bb5e2a2227d0"},
+    {file = "pydantic-1.10.8-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e4148e635994d57d834be1182a44bdb07dd867fa3c2d1b37002000646cc5459"},
+    {file = "pydantic-1.10.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12f7b0bf8553e310e530e9f3a2f5734c68699f42218bf3568ef49cd9b0e44df4"},
+    {file = "pydantic-1.10.8-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:42aa0c4b5c3025483240a25b09f3c09a189481ddda2ea3a831a9d25f444e03c1"},
+    {file = "pydantic-1.10.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:17aef11cc1b997f9d574b91909fed40761e13fac438d72b81f902226a69dac01"},
+    {file = "pydantic-1.10.8-cp39-cp39-win_amd64.whl", hash = "sha256:66a703d1983c675a6e0fed8953b0971c44dba48a929a2000a493c3772eb61a5a"},
+    {file = "pydantic-1.10.8-py3-none-any.whl", hash = "sha256:7456eb22ed9aaa24ff3e7b4757da20d9e5ce2a81018c1b3ebd81a0b88a18f3b2"},
+    {file = "pydantic-1.10.8.tar.gz", hash = "sha256:1410275520dfa70effadf4c21811d755e7ef9bb1f1d077a21958153a92c8d9ca"},
+]
+
+[package.dependencies]
+typing-extensions = ">=4.2.0"
+
+[package.extras]
+dotenv = ["python-dotenv (>=0.10.4)"]
+email = ["email-validator (>=1.0.3)"]
+
+[[package]]
+name = "pyee"
+version = "9.0.4"
+description = "A port of node.js's EventEmitter to python."
+optional = false
+python-versions = "*"
+files = [
+    {file = "pyee-9.0.4-py2.py3-none-any.whl", hash = "sha256:9f066570130c554e9cc12de5a9d86f57c7ee47fece163bbdaa3e9c933cfbdfa5"},
+    {file = "pyee-9.0.4.tar.gz", hash = "sha256:2770c4928abc721f46b705e6a72b0c59480c4a69c9a83ca0b00bb994f1ea4b32"},
+]
+
+[package.dependencies]
+typing-extensions = "*"
+
+[[package]]
+name = "pygments"
+version = "2.15.1"
+description = "Pygments is a syntax highlighting package written in Python."
+optional = false
+python-versions = ">=3.7"
+files = [
+    {file = "Pygments-2.15.1-py3-none-any.whl", hash = "sha256:db2db3deb4b4179f399a09054b023b6a586b76499d36965813c71aa8ed7b5fd1"},
+    {file = "Pygments-2.15.1.tar.gz", hash = "sha256:8ace4d3c1dd481894b2005f560ead0f9f19ee64fe983366be1a21e171d12775c"},
+]
+
+[package.extras]
+plugins = ["importlib-metadata"]
+
+[[package]]
+name = "pyparsing"
+version = "3.0.9"
+description = "pyparsing module - Classes and methods to define and execute parsing grammars"
+optional = false
+python-versions = ">=3.6.8"
+files = [
+    {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"},
+    {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"},
+]
+
+[package.extras]
+diagrams = ["jinja2", "railroad-diagrams"]
+
+[[package]]
+name = "python-dateutil"
+version = "2.8.2"
+description = "Extensions to the standard Python datetime module"
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
+files = [
+    {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"},
+    {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"},
+]
+
+[package.dependencies]
+six = ">=1.5"
+
+[[package]]
+name = "python-dotenv"
+version = "1.0.0"
+description = "Read key-value pairs from a .env file and set them as environment variables"
+optional = false
+python-versions = ">=3.8"
+files = [
+    {file = "python-dotenv-1.0.0.tar.gz", hash = "sha256:a8df96034aae6d2d50a4ebe8216326c61c3eb64836776504fcca410e5937a3ba"},
+    {file = "python_dotenv-1.0.0-py3-none-any.whl", hash = "sha256:f5971a9226b701070a4bf2c38c89e5a3f0d64de8debda981d1db98583009122a"},
+]
+
+[package.extras]
+cli = ["click (>=5.0)"]
+
+[[package]]
+name = "pyyaml"
+version = "6.0"
+description = "YAML parser and emitter for Python"
+optional = false
+python-versions = ">=3.6"
+files = [
+    {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"},
+    {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"},
+    {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"},
+    {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"},
+    {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"},
+    {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"},
+    {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"},
+    {file = "PyYAML-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358"},
+    {file = "PyYAML-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1"},
+    {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d"},
+    {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f"},
+    {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782"},
+    {file = "PyYAML-6.0-cp311-cp311-win32.whl", hash = "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7"},
+    {file = "PyYAML-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf"},
+    {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"},
+    {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"},
+    {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"},
+    {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"},
+    {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"},
+    {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"},
+    {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"},
+    {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"},
+    {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"},
+    {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"},
+    {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"},
+    {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"},
+    {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"},
+    {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"},
+    {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"},
+    {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"},
+    {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"},
+    {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"},
+    {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"},
+    {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"},
+    {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"},
+    {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"},
+    {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"},
+    {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"},
+    {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"},
+    {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"},
+]
+
+[[package]]
+name = "reactpy"
+version = "1.0.0"
+description = "Reactive user interfaces with pure Python"
+optional = false
+python-versions = ">=3.9"
+files = []
+develop = false
+
+[package.dependencies]
+anyio = ">=3"
+asgiref = ">=3"
+colorlog = ">=6"
+fastapi = {version = ">=0.63.0", optional = true, markers = "extra == \"fastapi\""}
+fastjsonschema = ">=2.14.5"
+flask = {version = "*", optional = true, markers = "extra == \"flask\""}
+flask-cors = {version = "*", optional = true, markers = "extra == \"flask\""}
+flask-sock = {version = "*", optional = true, markers = "extra == \"flask\""}
+jsonpatch = ">=1.32"
+lxml = ">=4"
+markupsafe = {version = ">=1.1.1,<2.1", optional = true, markers = "extra == \"flask\""}
+mypy-extensions = ">=0.4.3"
+playwright = {version = "*", optional = true, markers = "extra == \"testing\""}
+requests = ">=2"
+sanic = {version = ">=21", optional = true, markers = "extra == \"sanic\""}
+sanic-cors = {version = "*", optional = true, markers = "extra == \"sanic\""}
+starlette = {version = ">=0.13.6", optional = true, markers = "extra == \"starlette\""}
+tornado = {version = "*", optional = true, markers = "extra == \"tornado\""}
+typing-extensions = ">=3.10"
+uvicorn = {version = ">=0.19.0", extras = ["standard"], optional = true, markers = "extra == \"fastapi\" or extra == \"sanic\" or extra == \"starlette\""}
+
+[package.extras]
+all = ["reactpy[fastapi,flask,sanic,starlette,testing,tornado]"]
+fastapi = ["fastapi (>=0.63.0)", "uvicorn[standard] (>=0.19.0)"]
+flask = ["flask", "flask-cors", "flask-sock", "markupsafe (>=1.1.1,<2.1)"]
+sanic = ["sanic (>=21)", "sanic-cors", "uvicorn[standard] (>=0.19.0)"]
+starlette = ["starlette (>=0.13.6)", "uvicorn[standard] (>=0.19.0)"]
+testing = ["playwright"]
+tornado = ["tornado"]
+
+[package.source]
+type = "directory"
+url = "../src/py/reactpy"
+
+[[package]]
+name = "requests"
+version = "2.31.0"
+description = "Python HTTP for Humans."
+optional = false
+python-versions = ">=3.7"
+files = [
+    {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"},
+    {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"},
+]
+
+[package.dependencies]
+certifi = ">=2017.4.17"
+charset-normalizer = ">=2,<4"
+idna = ">=2.5,<4"
+urllib3 = ">=1.21.1,<3"
+
+[package.extras]
+socks = ["PySocks (>=1.5.6,!=1.5.7)"]
+use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
+
+[[package]]
+name = "sanic"
+version = "23.3.0"
+description = "A web server and web framework that's written to go fast. Build fast. Run fast."
+optional = false
+python-versions = ">=3.7"
+files = [
+    {file = "sanic-23.3.0-py3-none-any.whl", hash = "sha256:7cafbd63da9c6c6d8aeb8cb4304addf8a274352ab812014386c63e55f474fbee"},
+    {file = "sanic-23.3.0.tar.gz", hash = "sha256:b80ebc5c38c983cb45ae5ecc7a669a54c823ec1dff297fbd5f817b1e9e9e49af"},
+]
+
+[package.dependencies]
+aiofiles = ">=0.6.0"
+html5tagger = ">=1.2.1"
+httptools = ">=0.0.10"
+multidict = ">=5.0,<7.0"
+sanic-routing = ">=22.8.0"
+tracerite = ">=1.0.0"
+ujson = {version = ">=1.35", markers = "sys_platform != \"win32\" and implementation_name == \"cpython\""}
+uvloop = {version = ">=0.15.0", markers = "sys_platform != \"win32\" and implementation_name == \"cpython\""}
+websockets = ">=10.0"
+
+[package.extras]
+all = ["bandit", "beautifulsoup4", "black", "chardet (==3.*)", "coverage", "cryptography", "docutils", "enum-tools[sphinx]", "flake8", "isort (>=5.0.0)", "m2r2", "mistune (<2.0.0)", "mypy (>=0.901,<0.910)", "pygments", "pytest (==7.1.*)", "pytest-benchmark", "pytest-sanic", "sanic-testing (>=23.3.0)", "slotscheck (>=0.8.0,<1)", "sphinx (>=2.1.2)", "sphinx-rtd-theme (>=0.4.3)", "towncrier", "tox", "types-ujson", "uvicorn (<0.15.0)"]
+dev = ["bandit", "beautifulsoup4", "black", "chardet (==3.*)", "coverage", "cryptography", "docutils", "flake8", "isort (>=5.0.0)", "mypy (>=0.901,<0.910)", "pygments", "pytest (==7.1.*)", "pytest-benchmark", "pytest-sanic", "sanic-testing (>=23.3.0)", "slotscheck (>=0.8.0,<1)", "towncrier", "tox", "types-ujson", "uvicorn (<0.15.0)"]
+docs = ["docutils", "enum-tools[sphinx]", "m2r2", "mistune (<2.0.0)", "pygments", "sphinx (>=2.1.2)", "sphinx-rtd-theme (>=0.4.3)"]
+ext = ["sanic-ext"]
+http3 = ["aioquic"]
+test = ["bandit", "beautifulsoup4", "black", "chardet (==3.*)", "coverage", "docutils", "flake8", "isort (>=5.0.0)", "mypy (>=0.901,<0.910)", "pygments", "pytest (==7.1.*)", "pytest-benchmark", "pytest-sanic", "sanic-testing (>=23.3.0)", "slotscheck (>=0.8.0,<1)", "types-ujson", "uvicorn (<0.15.0)"]
+
+[[package]]
+name = "sanic-cors"
+version = "2.2.0"
+description = "A Sanic extension adding a decorator for CORS support. Based on flask-cors by Cory Dolphin."
+optional = false
+python-versions = "*"
+files = [
+    {file = "Sanic-Cors-2.2.0.tar.gz", hash = "sha256:f8d7515da4c8b837871d422c66314c4b5704396a78894b59c50e26aa72a95873"},
+    {file = "Sanic_Cors-2.2.0-py2.py3-none-any.whl", hash = "sha256:c3b133ff1f0bb609a53db35f727f5c371dc4ebeb6be4cc2c37c19dd8b9301115"},
+]
+
+[package.dependencies]
+packaging = ">=21.3"
+sanic = ">=21.9.3"
+
+[[package]]
+name = "sanic-routing"
+version = "22.8.0"
+description = "Core routing component for Sanic"
+optional = false
+python-versions = "*"
+files = [
+    {file = "sanic-routing-22.8.0.tar.gz", hash = "sha256:305729b4e0bf01f074044a2a315ff401fa7eeffb009eec1d2c81d35e1038ddfc"},
+    {file = "sanic_routing-22.8.0-py3-none-any.whl", hash = "sha256:9a928ed9e19a36bc019223be90a5da0ab88cdd76b101e032510b6a7073c017e9"},
+]
+
+[[package]]
+name = "simple-websocket"
+version = "0.10.0"
+description = "Simple WebSocket server and client for Python"
+optional = false
+python-versions = ">=3.6"
+files = [
+    {file = "simple-websocket-0.10.0.tar.gz", hash = "sha256:82c0b0b1006d5490f09ff66392394d90dd758285635edad241e093e9a8abd3eb"},
+    {file = "simple_websocket-0.10.0-py3-none-any.whl", hash = "sha256:fc1bc56c393a187e7268f8ab99da1a8e8da9b5dfb7769a2f3b8dada00067745b"},
+]
+
+[package.dependencies]
+wsproto = "*"
+
+[[package]]
+name = "six"
+version = "1.16.0"
+description = "Python 2 and 3 compatibility utilities"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
+files = [
+    {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
+    {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
+]
+
+[[package]]
+name = "sniffio"
+version = "1.3.0"
+description = "Sniff out which async library your code is running under"
+optional = false
+python-versions = ">=3.7"
+files = [
+    {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"},
+    {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"},
+]
+
+[[package]]
+name = "snowballstemmer"
+version = "2.2.0"
+description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms."
+optional = false
+python-versions = "*"
+files = [
+    {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"},
+    {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"},
+]
+
+[[package]]
+name = "soupsieve"
+version = "2.4.1"
+description = "A modern CSS selector implementation for Beautiful Soup."
+optional = false
+python-versions = ">=3.7"
+files = [
+    {file = "soupsieve-2.4.1-py3-none-any.whl", hash = "sha256:1c1bfee6819544a3447586c889157365a27e10d88cde3ad3da0cf0ddf646feb8"},
+    {file = "soupsieve-2.4.1.tar.gz", hash = "sha256:89d12b2d5dfcd2c9e8c22326da9d9aa9cb3dfab0a83a024f05704076ee8d35ea"},
+]
+
+[[package]]
+name = "sphinx"
+version = "4.5.0"
+description = "Python documentation generator"
+optional = false
+python-versions = ">=3.6"
+files = [
+    {file = "Sphinx-4.5.0-py3-none-any.whl", hash = "sha256:ebf612653238bcc8f4359627a9b7ce44ede6fdd75d9d30f68255c7383d3a6226"},
+    {file = "Sphinx-4.5.0.tar.gz", hash = "sha256:7bf8ca9637a4ee15af412d1a1d9689fec70523a68ca9bb9127c2f3eeb344e2e6"},
+]
+
+[package.dependencies]
+alabaster = ">=0.7,<0.8"
+babel = ">=1.3"
+colorama = {version = ">=0.3.5", markers = "sys_platform == \"win32\""}
+docutils = ">=0.14,<0.18"
+imagesize = "*"
+importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""}
+Jinja2 = ">=2.3"
+packaging = "*"
+Pygments = ">=2.0"
+requests = ">=2.5.0"
+snowballstemmer = ">=1.1"
+sphinxcontrib-applehelp = "*"
+sphinxcontrib-devhelp = "*"
+sphinxcontrib-htmlhelp = ">=2.0.0"
+sphinxcontrib-jsmath = "*"
+sphinxcontrib-qthelp = "*"
+sphinxcontrib-serializinghtml = ">=1.1.5"
+
+[package.extras]
+docs = ["sphinxcontrib-websupport"]
+lint = ["docutils-stubs", "flake8 (>=3.5.0)", "isort", "mypy (>=0.931)", "types-requests", "types-typed-ast"]
+test = ["cython", "html5lib", "pytest", "pytest-cov", "typed-ast"]
+
+[[package]]
+name = "sphinx-autobuild"
+version = "2021.3.14"
+description = "Rebuild Sphinx documentation on changes, with live-reload in the browser."
+optional = false
+python-versions = ">=3.6"
+files = [
+    {file = "sphinx-autobuild-2021.3.14.tar.gz", hash = "sha256:de1ca3b66e271d2b5b5140c35034c89e47f263f2cd5db302c9217065f7443f05"},
+    {file = "sphinx_autobuild-2021.3.14-py3-none-any.whl", hash = "sha256:8fe8cbfdb75db04475232f05187c776f46f6e9e04cacf1e49ce81bdac649ccac"},
+]
+
+[package.dependencies]
+colorama = "*"
+livereload = "*"
+sphinx = "*"
+
+[package.extras]
+test = ["pytest", "pytest-cov"]
+
+[[package]]
+name = "sphinx-autodoc-typehints"
+version = "1.19.1"
+description = "Type hints (PEP 484) support for the Sphinx autodoc extension"
+optional = false
+python-versions = ">=3.7"
+files = [
+    {file = "sphinx_autodoc_typehints-1.19.1-py3-none-any.whl", hash = "sha256:9be46aeeb1b315eb5df1f3a7cb262149895d16c7d7dcd77b92513c3c3a1e85e6"},
+    {file = "sphinx_autodoc_typehints-1.19.1.tar.gz", hash = "sha256:6c841db55e0e9be0483ff3962a2152b60e79306f4288d8c4e7e86ac84486a5ea"},
+]
+
+[package.dependencies]
+Sphinx = ">=4.5"
+
+[package.extras]
+testing = ["covdefaults (>=2.2)", "coverage (>=6.3)", "diff-cover (>=6.4)", "nptyping (>=2.1.2)", "pytest (>=7.1)", "pytest-cov (>=3)", "sphobjinv (>=2)", "typing-extensions (>=4.1)"]
+type-comments = ["typed-ast (>=1.5.2)"]
+
+[[package]]
+name = "sphinx-copybutton"
+version = "0.5.2"
+description = "Add a copy button to each of your code cells."
+optional = false
+python-versions = ">=3.7"
+files = [
+    {file = "sphinx-copybutton-0.5.2.tar.gz", hash = "sha256:4cf17c82fb9646d1bc9ca92ac280813a3b605d8c421225fd9913154103ee1fbd"},
+    {file = "sphinx_copybutton-0.5.2-py3-none-any.whl", hash = "sha256:fb543fd386d917746c9a2c50360c7905b605726b9355cd26e9974857afeae06e"},
+]
+
+[package.dependencies]
+sphinx = ">=1.8"
+
+[package.extras]
+code-style = ["pre-commit (==2.12.1)"]
+rtd = ["ipython", "myst-nb", "sphinx", "sphinx-book-theme", "sphinx-examples"]
+
+[[package]]
+name = "sphinx-design"
+version = "0.4.1"
+description = "A sphinx extension for designing beautiful, view size responsive web components."
+optional = false
+python-versions = ">=3.7"
+files = [
+    {file = "sphinx_design-0.4.1-py3-none-any.whl", hash = "sha256:23bf5705eb31296d4451f68b0222a698a8a84396ffe8378dfd9319ba7ab8efd9"},
+    {file = "sphinx_design-0.4.1.tar.gz", hash = "sha256:5b6418ba4a2dc3d83592ea0ff61a52a891fe72195a4c3a18b2fa1c7668ce4708"},
+]
+
+[package.dependencies]
+sphinx = ">=4,<7"
+
+[package.extras]
+code-style = ["pre-commit (>=2.12,<3.0)"]
+rtd = ["myst-parser (>=0.18.0,<2)"]
+testing = ["myst-parser (>=0.18.0,<2)", "pytest (>=7.1,<8.0)", "pytest-cov", "pytest-regressions"]
+theme-furo = ["furo (>=2022.06.04,<2022.07)"]
+theme-pydata = ["pydata-sphinx-theme (>=0.9.0,<0.10.0)"]
+theme-rtd = ["sphinx-rtd-theme (>=1.0,<2.0)"]
+theme-sbt = ["sphinx-book-theme (>=0.3.0,<0.4.0)"]
+
+[[package]]
+name = "sphinx-reredirects"
+version = "0.1.2"
+description = "Handles redirects for moved pages in Sphinx documentation projects"
+optional = false
+python-versions = ">=3.5"
+files = [
+    {file = "sphinx_reredirects-0.1.2-py3-none-any.whl", hash = "sha256:3a22161771aadd448bb608a4fe7277252182a337af53c18372b7104531d71489"},
+    {file = "sphinx_reredirects-0.1.2.tar.gz", hash = "sha256:a0e7213304759b01edc22f032f1715a1c61176fc8f167164e7a52b9feec9ac64"},
+]
+
+[package.dependencies]
+sphinx = "*"
+
+[[package]]
+name = "sphinx-resolve-py-references"
+version = "0.1.0"
+description = "Better python object resolution in Sphinx"
+optional = false
+python-versions = ">=3.7"
+files = [
+    {file = "sphinx_resolve_py_references-0.1.0-py2.py3-none-any.whl", hash = "sha256:ccf44a6b62d75c3a568285f4e1815734088c1a7cab7bbb7935bb22fbf0d78bc2"},
+    {file = "sphinx_resolve_py_references-0.1.0.tar.gz", hash = "sha256:0f87c06b29ec128964aee2e40d170d1d3c0e5f4955b2618a89ca724f42385372"},
+]
+
+[package.dependencies]
+sphinx = "*"
+
+[[package]]
+name = "sphinxcontrib-applehelp"
+version = "1.0.4"
+description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books"
+optional = false
+python-versions = ">=3.8"
+files = [
+    {file = "sphinxcontrib-applehelp-1.0.4.tar.gz", hash = "sha256:828f867945bbe39817c210a1abfd1bc4895c8b73fcaade56d45357a348a07d7e"},
+    {file = "sphinxcontrib_applehelp-1.0.4-py3-none-any.whl", hash = "sha256:29d341f67fb0f6f586b23ad80e072c8e6ad0b48417db2bde114a4c9746feb228"},
+]
+
+[package.extras]
+lint = ["docutils-stubs", "flake8", "mypy"]
+test = ["pytest"]
+
+[[package]]
+name = "sphinxcontrib-devhelp"
+version = "1.0.2"
+description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document."
+optional = false
+python-versions = ">=3.5"
+files = [
+    {file = "sphinxcontrib-devhelp-1.0.2.tar.gz", hash = "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"},
+    {file = "sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e"},
+]
+
+[package.extras]
+lint = ["docutils-stubs", "flake8", "mypy"]
+test = ["pytest"]
+
+[[package]]
+name = "sphinxcontrib-htmlhelp"
+version = "2.0.1"
+description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files"
+optional = false
+python-versions = ">=3.8"
+files = [
+    {file = "sphinxcontrib-htmlhelp-2.0.1.tar.gz", hash = "sha256:0cbdd302815330058422b98a113195c9249825d681e18f11e8b1f78a2f11efff"},
+    {file = "sphinxcontrib_htmlhelp-2.0.1-py3-none-any.whl", hash = "sha256:c38cb46dccf316c79de6e5515e1770414b797162b23cd3d06e67020e1d2a6903"},
+]
+
+[package.extras]
+lint = ["docutils-stubs", "flake8", "mypy"]
+test = ["html5lib", "pytest"]
+
+[[package]]
+name = "sphinxcontrib-jsmath"
+version = "1.0.1"
+description = "A sphinx extension which renders display math in HTML via JavaScript"
+optional = false
+python-versions = ">=3.5"
+files = [
+    {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"},
+    {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"},
+]
+
+[package.extras]
+test = ["flake8", "mypy", "pytest"]
+
+[[package]]
+name = "sphinxcontrib-qthelp"
+version = "1.0.3"
+description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document."
+optional = false
+python-versions = ">=3.5"
+files = [
+    {file = "sphinxcontrib-qthelp-1.0.3.tar.gz", hash = "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72"},
+    {file = "sphinxcontrib_qthelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"},
+]
+
+[package.extras]
+lint = ["docutils-stubs", "flake8", "mypy"]
+test = ["pytest"]
+
+[[package]]
+name = "sphinxcontrib-serializinghtml"
+version = "1.1.5"
+description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)."
+optional = false
+python-versions = ">=3.5"
+files = [
+    {file = "sphinxcontrib-serializinghtml-1.1.5.tar.gz", hash = "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952"},
+    {file = "sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd"},
+]
+
+[package.extras]
+lint = ["docutils-stubs", "flake8", "mypy"]
+test = ["pytest"]
+
+[[package]]
+name = "sphinxext-opengraph"
+version = "0.8.2"
+description = "Sphinx Extension to enable OGP support"
+optional = false
+python-versions = ">=3.7"
+files = [
+    {file = "sphinxext-opengraph-0.8.2.tar.gz", hash = "sha256:45a693b6704052c426576f0a1f630649c55b4188bc49eb63e9587e24a923db39"},
+    {file = "sphinxext_opengraph-0.8.2-py3-none-any.whl", hash = "sha256:6a05bdfe5176d9dd0a1d58a504f17118362ab976631213cd36fb44c4c40544c9"},
+]
+
+[package.dependencies]
+matplotlib = "*"
+sphinx = ">=4.0"
+
+[[package]]
+name = "starlette"
+version = "0.27.0"
+description = "The little ASGI library that shines."
+optional = false
+python-versions = ">=3.7"
+files = [
+    {file = "starlette-0.27.0-py3-none-any.whl", hash = "sha256:918416370e846586541235ccd38a474c08b80443ed31c578a418e2209b3eef91"},
+    {file = "starlette-0.27.0.tar.gz", hash = "sha256:6a6b0d042acb8d469a01eba54e9cda6cbd24ac602c4cd016723117d6a7e73b75"},
+]
+
+[package.dependencies]
+anyio = ">=3.4.0,<5"
+typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""}
+
+[package.extras]
+full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart", "pyyaml"]
+
+[[package]]
+name = "tornado"
+version = "6.3.2"
+description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed."
+optional = false
+python-versions = ">= 3.8"
+files = [
+    {file = "tornado-6.3.2-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:c367ab6c0393d71171123ca5515c61ff62fe09024fa6bf299cd1339dc9456829"},
+    {file = "tornado-6.3.2-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:b46a6ab20f5c7c1cb949c72c1994a4585d2eaa0be4853f50a03b5031e964fc7c"},
+    {file = "tornado-6.3.2-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c2de14066c4a38b4ecbbcd55c5cc4b5340eb04f1c5e81da7451ef555859c833f"},
+    {file = "tornado-6.3.2-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:05615096845cf50a895026f749195bf0b10b8909f9be672f50b0fe69cba368e4"},
+    {file = "tornado-6.3.2-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5b17b1cf5f8354efa3d37c6e28fdfd9c1c1e5122f2cb56dac121ac61baa47cbe"},
+    {file = "tornado-6.3.2-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:29e71c847a35f6e10ca3b5c2990a52ce38b233019d8e858b755ea6ce4dcdd19d"},
+    {file = "tornado-6.3.2-cp38-abi3-musllinux_1_1_i686.whl", hash = "sha256:834ae7540ad3a83199a8da8f9f2d383e3c3d5130a328889e4cc991acc81e87a0"},
+    {file = "tornado-6.3.2-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:6a0848f1aea0d196a7c4f6772197cbe2abc4266f836b0aac76947872cd29b411"},
+    {file = "tornado-6.3.2-cp38-abi3-win32.whl", hash = "sha256:7efcbcc30b7c654eb6a8c9c9da787a851c18f8ccd4a5a3a95b05c7accfa068d2"},
+    {file = "tornado-6.3.2-cp38-abi3-win_amd64.whl", hash = "sha256:0c325e66c8123c606eea33084976c832aa4e766b7dff8aedd7587ea44a604cdf"},
+    {file = "tornado-6.3.2.tar.gz", hash = "sha256:4b927c4f19b71e627b13f3db2324e4ae660527143f9e1f2e2fb404f3a187e2ba"},
+]
+
+[[package]]
+name = "tracerite"
+version = "1.1.0"
+description = "Human-readable HTML tracebacks for Python exceptions"
+optional = false
+python-versions = "*"
+files = [
+    {file = "tracerite-1.1.0-py3-none-any.whl", hash = "sha256:4cccac04db05eeeabda45e72b57199e147fa2f73cf64d89cfd625df321bd2ab6"},
+    {file = "tracerite-1.1.0.tar.gz", hash = "sha256:041dab8fd4bb405f73506293ac7438a2d311e5f9044378ba7d9a6540392f9e4b"},
+]
+
+[package.dependencies]
+html5tagger = ">=1.2.1"
+
+[[package]]
+name = "typing-extensions"
+version = "4.6.3"
+description = "Backported and Experimental Type Hints for Python 3.7+"
+optional = false
+python-versions = ">=3.7"
+files = [
+    {file = "typing_extensions-4.6.3-py3-none-any.whl", hash = "sha256:88a4153d8505aabbb4e13aacb7c486c2b4a33ca3b3f807914a9b4c844c471c26"},
+    {file = "typing_extensions-4.6.3.tar.gz", hash = "sha256:d91d5919357fe7f681a9f2b5b4cb2a5f1ef0a1e9f59c4d8ff0d3491e05c0ffd5"},
+]
+
+[[package]]
+name = "ujson"
+version = "5.7.0"
+description = "Ultra fast JSON encoder and decoder for Python"
+optional = false
+python-versions = ">=3.7"
+files = [
+    {file = "ujson-5.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5eba5e69e4361ac3a311cf44fa71bc619361b6e0626768a494771aacd1c2f09b"},
+    {file = "ujson-5.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:aae4d9e1b4c7b61780f0a006c897a4a1904f862fdab1abb3ea8f45bd11aa58f3"},
+    {file = "ujson-5.7.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2e43ccdba1cb5c6d3448eadf6fc0dae7be6c77e357a3abc968d1b44e265866d"},
+    {file = "ujson-5.7.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54384ce4920a6d35fa9ea8e580bc6d359e3eb961fa7e43f46c78e3ed162d56ff"},
+    {file = "ujson-5.7.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:24ad1aa7fc4e4caa41d3d343512ce68e41411fb92adf7f434a4d4b3749dc8f58"},
+    {file = "ujson-5.7.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:afff311e9f065a8f03c3753db7011bae7beb73a66189c7ea5fcb0456b7041ea4"},
+    {file = "ujson-5.7.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6e80f0d03e7e8646fc3d79ed2d875cebd4c83846e129737fdc4c2532dbd43d9e"},
+    {file = "ujson-5.7.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:137831d8a0db302fb6828ee21c67ad63ac537bddc4376e1aab1c8573756ee21c"},
+    {file = "ujson-5.7.0-cp310-cp310-win32.whl", hash = "sha256:7df3fd35ebc14dafeea031038a99232b32f53fa4c3ecddb8bed132a43eefb8ad"},
+    {file = "ujson-5.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:af4639f684f425177d09ae409c07602c4096a6287027469157bfb6f83e01448b"},
+    {file = "ujson-5.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9b0f2680ce8a70f77f5d70aaf3f013d53e6af6d7058727a35d8ceb4a71cdd4e9"},
+    {file = "ujson-5.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:67a19fd8e7d8cc58a169bea99fed5666023adf707a536d8f7b0a3c51dd498abf"},
+    {file = "ujson-5.7.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6abb8e6d8f1ae72f0ed18287245f5b6d40094e2656d1eab6d99d666361514074"},
+    {file = "ujson-5.7.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8cd622c069368d5074bd93817b31bdb02f8d818e57c29e206f10a1f9c6337dd"},
+    {file = "ujson-5.7.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:14f9082669f90e18e64792b3fd0bf19f2b15e7fe467534a35ea4b53f3bf4b755"},
+    {file = "ujson-5.7.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d7ff6ebb43bc81b057724e89550b13c9a30eda0f29c2f506f8b009895438f5a6"},
+    {file = "ujson-5.7.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:f7f241488879d91a136b299e0c4ce091996c684a53775e63bb442d1a8e9ae22a"},
+    {file = "ujson-5.7.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5593263a7fcfb934107444bcfba9dde8145b282de0ee9f61e285e59a916dda0f"},
+    {file = "ujson-5.7.0-cp311-cp311-win32.whl", hash = "sha256:26c2b32b489c393106e9cb68d0a02e1a7b9d05a07429d875c46b94ee8405bdb7"},
+    {file = "ujson-5.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:ed24406454bb5a31df18f0a423ae14beb27b28cdfa34f6268e7ebddf23da807e"},
+    {file = "ujson-5.7.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:18679484e3bf9926342b1c43a3bd640f93a9eeeba19ef3d21993af7b0c44785d"},
+    {file = "ujson-5.7.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ee295761e1c6c30400641f0a20d381633d7622633cdf83a194f3c876a0e4b7e"},
+    {file = "ujson-5.7.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b738282e12a05f400b291966630a98d622da0938caa4bc93cf65adb5f4281c60"},
+    {file = "ujson-5.7.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00343501dbaa5172e78ef0e37f9ebd08040110e11c12420ff7c1f9f0332d939e"},
+    {file = "ujson-5.7.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c0d1f7c3908357ee100aa64c4d1cf91edf99c40ac0069422a4fd5fd23b263263"},
+    {file = "ujson-5.7.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:a5d2f44331cf04689eafac7a6596c71d6657967c07ac700b0ae1c921178645da"},
+    {file = "ujson-5.7.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:16b2254a77b310f118717715259a196662baa6b1f63b1a642d12ab1ff998c3d7"},
+    {file = "ujson-5.7.0-cp37-cp37m-win32.whl", hash = "sha256:6faf46fa100b2b89e4db47206cf8a1ffb41542cdd34dde615b2fc2288954f194"},
+    {file = "ujson-5.7.0-cp37-cp37m-win_amd64.whl", hash = "sha256:ff0004c3f5a9a6574689a553d1b7819d1a496b4f005a7451f339dc2d9f4cf98c"},
+    {file = "ujson-5.7.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:75204a1dd7ec6158c8db85a2f14a68d2143503f4bafb9a00b63fe09d35762a5e"},
+    {file = "ujson-5.7.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7312731c7826e6c99cdd3ac503cd9acd300598e7a80bcf41f604fee5f49f566c"},
+    {file = "ujson-5.7.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b9dc5a90e2149643df7f23634fe202fed5ebc787a2a1be95cf23632b4d90651"},
+    {file = "ujson-5.7.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6a6961fc48821d84b1198a09516e396d56551e910d489692126e90bf4887d29"},
+    {file = "ujson-5.7.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b01a9af52a0d5c46b2c68e3f258fdef2eacaa0ce6ae3e9eb97983f5b1166edb6"},
+    {file = "ujson-5.7.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b7316d3edeba8a403686cdcad4af737b8415493101e7462a70ff73dd0609eafc"},
+    {file = "ujson-5.7.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4ee997799a23227e2319a3f8817ce0b058923dbd31904761b788dc8f53bd3e30"},
+    {file = "ujson-5.7.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:dda9aa4c33435147262cd2ea87c6b7a1ca83ba9b3933ff7df34e69fee9fced0c"},
+    {file = "ujson-5.7.0-cp38-cp38-win32.whl", hash = "sha256:bea8d30e362180aafecabbdcbe0e1f0b32c9fa9e39c38e4af037b9d3ca36f50c"},
+    {file = "ujson-5.7.0-cp38-cp38-win_amd64.whl", hash = "sha256:c96e3b872bf883090ddf32cc41957edf819c5336ab0007d0cf3854e61841726d"},
+    {file = "ujson-5.7.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6411aea4c94a8e93c2baac096fbf697af35ba2b2ed410b8b360b3c0957a952d3"},
+    {file = "ujson-5.7.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3d3b3499c55911f70d4e074c626acdb79a56f54262c3c83325ffb210fb03e44d"},
+    {file = "ujson-5.7.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:341f891d45dd3814d31764626c55d7ab3fd21af61fbc99d070e9c10c1190680b"},
+    {file = "ujson-5.7.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f242eec917bafdc3f73a1021617db85f9958df80f267db69c76d766058f7b19"},
+    {file = "ujson-5.7.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c3af9f9f22a67a8c9466a32115d9073c72a33ae627b11de6f592df0ee09b98b6"},
+    {file = "ujson-5.7.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4a3d794afbf134df3056a813e5c8a935208cddeae975bd4bc0ef7e89c52f0ce0"},
+    {file = "ujson-5.7.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:800bf998e78dae655008dd10b22ca8dc93bdcfcc82f620d754a411592da4bbf2"},
+    {file = "ujson-5.7.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b5ac3d5c5825e30b438ea92845380e812a476d6c2a1872b76026f2e9d8060fc2"},
+    {file = "ujson-5.7.0-cp39-cp39-win32.whl", hash = "sha256:cd90027e6d93e8982f7d0d23acf88c896d18deff1903dd96140613389b25c0dd"},
+    {file = "ujson-5.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:523ee146cdb2122bbd827f4dcc2a8e66607b3f665186bce9e4f78c9710b6d8ab"},
+    {file = "ujson-5.7.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e87cec407ec004cf1b04c0ed7219a68c12860123dfb8902ef880d3d87a71c172"},
+    {file = "ujson-5.7.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bab10165db6a7994e67001733f7f2caf3400b3e11538409d8756bc9b1c64f7e8"},
+    {file = "ujson-5.7.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b522be14a28e6ac1cf818599aeff1004a28b42df4ed4d7bc819887b9dac915fc"},
+    {file = "ujson-5.7.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7592f40175c723c032cdbe9fe5165b3b5903604f774ab0849363386e99e1f253"},
+    {file = "ujson-5.7.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:ed22f9665327a981f288a4f758a432824dc0314e4195a0eaeb0da56a477da94d"},
+    {file = "ujson-5.7.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:adf445a49d9a97a5a4c9bb1d652a1528de09dd1c48b29f79f3d66cea9f826bf6"},
+    {file = "ujson-5.7.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64772a53f3c4b6122ed930ae145184ebaed38534c60f3d859d8c3f00911eb122"},
+    {file = "ujson-5.7.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35209cb2c13fcb9d76d249286105b4897b75a5e7f0efb0c0f4b90f222ce48910"},
+    {file = "ujson-5.7.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:90712dfc775b2c7a07d4d8e059dd58636bd6ff1776d79857776152e693bddea6"},
+    {file = "ujson-5.7.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:0e4e8981c6e7e9e637e637ad8ffe948a09e5434bc5f52ecbb82b4b4cfc092bfb"},
+    {file = "ujson-5.7.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:581c945b811a3d67c27566539bfcb9705ea09cb27c4be0002f7a553c8886b817"},
+    {file = "ujson-5.7.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d36a807a24c7d44f71686685ae6fbc8793d784bca1adf4c89f5f780b835b6243"},
+    {file = "ujson-5.7.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b4257307e3662aa65e2644a277ca68783c5d51190ed9c49efebdd3cbfd5fa44"},
+    {file = "ujson-5.7.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea7423d8a2f9e160c5e011119741682414c5b8dce4ae56590a966316a07a4618"},
+    {file = "ujson-5.7.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:4c592eb91a5968058a561d358d0fef59099ed152cfb3e1cd14eee51a7a93879e"},
+    {file = "ujson-5.7.0.tar.gz", hash = "sha256:e788e5d5dcae8f6118ac9b45d0b891a0d55f7ac480eddcb7f07263f2bcf37b23"},
+]
+
+[[package]]
+name = "urllib3"
+version = "2.0.2"
+description = "HTTP library with thread-safe connection pooling, file post, and more."
+optional = false
+python-versions = ">=3.7"
+files = [
+    {file = "urllib3-2.0.2-py3-none-any.whl", hash = "sha256:d055c2f9d38dc53c808f6fdc8eab7360b6fdbbde02340ed25cfbcd817c62469e"},
+    {file = "urllib3-2.0.2.tar.gz", hash = "sha256:61717a1095d7e155cdb737ac7bb2f4324a858a1e2e6466f6d03ff630ca68d3cc"},
+]
+
+[package.extras]
+brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"]
+secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17.1.0)", "urllib3-secure-extra"]
+socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"]
+zstd = ["zstandard (>=0.18.0)"]
+
+[[package]]
+name = "uvicorn"
+version = "0.22.0"
+description = "The lightning-fast ASGI server."
+optional = false
+python-versions = ">=3.7"
+files = [
+    {file = "uvicorn-0.22.0-py3-none-any.whl", hash = "sha256:e9434d3bbf05f310e762147f769c9f21235ee118ba2d2bf1155a7196448bd996"},
+    {file = "uvicorn-0.22.0.tar.gz", hash = "sha256:79277ae03db57ce7d9aa0567830bbb51d7a612f54d6e1e3e92da3ef24c2c8ed8"},
+]
+
+[package.dependencies]
+click = ">=7.0"
+colorama = {version = ">=0.4", optional = true, markers = "sys_platform == \"win32\" and extra == \"standard\""}
+h11 = ">=0.8"
+httptools = {version = ">=0.5.0", optional = true, markers = "extra == \"standard\""}
+python-dotenv = {version = ">=0.13", optional = true, markers = "extra == \"standard\""}
+pyyaml = {version = ">=5.1", optional = true, markers = "extra == \"standard\""}
+uvloop = {version = ">=0.14.0,<0.15.0 || >0.15.0,<0.15.1 || >0.15.1", optional = true, markers = "(sys_platform != \"win32\" and sys_platform != \"cygwin\") and platform_python_implementation != \"PyPy\" and extra == \"standard\""}
+watchfiles = {version = ">=0.13", optional = true, markers = "extra == \"standard\""}
+websockets = {version = ">=10.4", optional = true, markers = "extra == \"standard\""}
+
+[package.extras]
+standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"]
+
+[[package]]
+name = "uvloop"
+version = "0.17.0"
+description = "Fast implementation of asyncio event loop on top of libuv"
+optional = false
+python-versions = ">=3.7"
+files = [
+    {file = "uvloop-0.17.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ce9f61938d7155f79d3cb2ffa663147d4a76d16e08f65e2c66b77bd41b356718"},
+    {file = "uvloop-0.17.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:68532f4349fd3900b839f588972b3392ee56042e440dd5873dfbbcd2cc67617c"},
+    {file = "uvloop-0.17.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0949caf774b9fcefc7c5756bacbbbd3fc4c05a6b7eebc7c7ad6f825b23998d6d"},
+    {file = "uvloop-0.17.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff3d00b70ce95adce264462c930fbaecb29718ba6563db354608f37e49e09024"},
+    {file = "uvloop-0.17.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a5abddb3558d3f0a78949c750644a67be31e47936042d4f6c888dd6f3c95f4aa"},
+    {file = "uvloop-0.17.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8efcadc5a0003d3a6e887ccc1fb44dec25594f117a94e3127954c05cf144d811"},
+    {file = "uvloop-0.17.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3378eb62c63bf336ae2070599e49089005771cc651c8769aaad72d1bd9385a7c"},
+    {file = "uvloop-0.17.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6aafa5a78b9e62493539456f8b646f85abc7093dd997f4976bb105537cf2635e"},
+    {file = "uvloop-0.17.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c686a47d57ca910a2572fddfe9912819880b8765e2f01dc0dd12a9bf8573e539"},
+    {file = "uvloop-0.17.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:864e1197139d651a76c81757db5eb199db8866e13acb0dfe96e6fc5d1cf45fc4"},
+    {file = "uvloop-0.17.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:2a6149e1defac0faf505406259561bc14b034cdf1d4711a3ddcdfbaa8d825a05"},
+    {file = "uvloop-0.17.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6708f30db9117f115eadc4f125c2a10c1a50d711461699a0cbfaa45b9a78e376"},
+    {file = "uvloop-0.17.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:23609ca361a7fc587031429fa25ad2ed7242941adec948f9d10c045bfecab06b"},
+    {file = "uvloop-0.17.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2deae0b0fb00a6af41fe60a675cec079615b01d68beb4cc7b722424406b126a8"},
+    {file = "uvloop-0.17.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45cea33b208971e87a31c17622e4b440cac231766ec11e5d22c76fab3bf9df62"},
+    {file = "uvloop-0.17.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:9b09e0f0ac29eee0451d71798878eae5a4e6a91aa275e114037b27f7db72702d"},
+    {file = "uvloop-0.17.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:dbbaf9da2ee98ee2531e0c780455f2841e4675ff580ecf93fe5c48fe733b5667"},
+    {file = "uvloop-0.17.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a4aee22ece20958888eedbad20e4dbb03c37533e010fb824161b4f05e641f738"},
+    {file = "uvloop-0.17.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:307958f9fc5c8bb01fad752d1345168c0abc5d62c1b72a4a8c6c06f042b45b20"},
+    {file = "uvloop-0.17.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ebeeec6a6641d0adb2ea71dcfb76017602ee2bfd8213e3fcc18d8f699c5104f"},
+    {file = "uvloop-0.17.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1436c8673c1563422213ac6907789ecb2b070f5939b9cbff9ef7113f2b531595"},
+    {file = "uvloop-0.17.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8887d675a64cfc59f4ecd34382e5b4f0ef4ae1da37ed665adba0c2badf0d6578"},
+    {file = "uvloop-0.17.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3db8de10ed684995a7f34a001f15b374c230f7655ae840964d51496e2f8a8474"},
+    {file = "uvloop-0.17.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7d37dccc7ae63e61f7b96ee2e19c40f153ba6ce730d8ba4d3b4e9738c1dccc1b"},
+    {file = "uvloop-0.17.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cbbe908fda687e39afd6ea2a2f14c2c3e43f2ca88e3a11964b297822358d0e6c"},
+    {file = "uvloop-0.17.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d97672dc709fa4447ab83276f344a165075fd9f366a97b712bdd3fee05efae8"},
+    {file = "uvloop-0.17.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1e507c9ee39c61bfddd79714e4f85900656db1aec4d40c6de55648e85c2799c"},
+    {file = "uvloop-0.17.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c092a2c1e736086d59ac8e41f9c98f26bbf9b9222a76f21af9dfe949b99b2eb9"},
+    {file = "uvloop-0.17.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:30babd84706115626ea78ea5dbc7dd8d0d01a2e9f9b306d24ca4ed5796c66ded"},
+    {file = "uvloop-0.17.0.tar.gz", hash = "sha256:0ddf6baf9cf11a1a22c71487f39f15b2cf78eb5bde7e5b45fbb99e8a9d91b9e1"},
+]
+
+[package.extras]
+dev = ["Cython (>=0.29.32,<0.30.0)", "Sphinx (>=4.1.2,<4.2.0)", "aiohttp", "flake8 (>=3.9.2,<3.10.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=22.0.0,<22.1.0)", "pycodestyle (>=2.7.0,<2.8.0)", "pytest (>=3.6.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"]
+docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"]
+test = ["Cython (>=0.29.32,<0.30.0)", "aiohttp", "flake8 (>=3.9.2,<3.10.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=22.0.0,<22.1.0)", "pycodestyle (>=2.7.0,<2.8.0)"]
+
+[[package]]
+name = "watchfiles"
+version = "0.19.0"
+description = "Simple, modern and high performance file watching and code reload in python."
+optional = false
+python-versions = ">=3.7"
+files = [
+    {file = "watchfiles-0.19.0-cp37-abi3-macosx_10_7_x86_64.whl", hash = "sha256:91633e64712df3051ca454ca7d1b976baf842d7a3640b87622b323c55f3345e7"},
+    {file = "watchfiles-0.19.0-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:b6577b8c6c8701ba8642ea9335a129836347894b666dd1ec2226830e263909d3"},
+    {file = "watchfiles-0.19.0-cp37-abi3-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:18b28f6ad871b82df9542ff958d0c86bb0d8310bb09eb8e87d97318a3b5273af"},
+    {file = "watchfiles-0.19.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fac19dc9cbc34052394dbe81e149411a62e71999c0a19e1e09ce537867f95ae0"},
+    {file = "watchfiles-0.19.0-cp37-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:09ea3397aecbc81c19ed7f025e051a7387feefdb789cf768ff994c1228182fda"},
+    {file = "watchfiles-0.19.0-cp37-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c0376deac92377817e4fb8f347bf559b7d44ff556d9bc6f6208dd3f79f104aaf"},
+    {file = "watchfiles-0.19.0-cp37-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c75eff897786ee262c9f17a48886f4e98e6cfd335e011c591c305e5d083c056"},
+    {file = "watchfiles-0.19.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb5d45c4143c1dd60f98a16187fd123eda7248f84ef22244818c18d531a249d1"},
+    {file = "watchfiles-0.19.0-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:79c533ff593db861ae23436541f481ec896ee3da4e5db8962429b441bbaae16e"},
+    {file = "watchfiles-0.19.0-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:3d7d267d27aceeeaa3de0dd161a0d64f0a282264d592e335fff7958cc0cbae7c"},
+    {file = "watchfiles-0.19.0-cp37-abi3-win32.whl", hash = "sha256:176a9a7641ec2c97b24455135d58012a5be5c6217fc4d5fef0b2b9f75dbf5154"},
+    {file = "watchfiles-0.19.0-cp37-abi3-win_amd64.whl", hash = "sha256:945be0baa3e2440151eb3718fd8846751e8b51d8de7b884c90b17d271d34cae8"},
+    {file = "watchfiles-0.19.0-cp37-abi3-win_arm64.whl", hash = "sha256:0089c6dc24d436b373c3c57657bf4f9a453b13767150d17284fc6162b2791911"},
+    {file = "watchfiles-0.19.0-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:cae3dde0b4b2078f31527acff6f486e23abed307ba4d3932466ba7cdd5ecec79"},
+    {file = "watchfiles-0.19.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:7f3920b1285a7d3ce898e303d84791b7bf40d57b7695ad549dc04e6a44c9f120"},
+    {file = "watchfiles-0.19.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9afd0d69429172c796164fd7fe8e821ade9be983f51c659a38da3faaaaac44dc"},
+    {file = "watchfiles-0.19.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68dce92b29575dda0f8d30c11742a8e2b9b8ec768ae414b54f7453f27bdf9545"},
+    {file = "watchfiles-0.19.0-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:5569fc7f967429d4bc87e355cdfdcee6aabe4b620801e2cf5805ea245c06097c"},
+    {file = "watchfiles-0.19.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:5471582658ea56fca122c0f0d0116a36807c63fefd6fdc92c71ca9a4491b6b48"},
+    {file = "watchfiles-0.19.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b538014a87f94d92f98f34d3e6d2635478e6be6423a9ea53e4dd96210065e193"},
+    {file = "watchfiles-0.19.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20b44221764955b1e703f012c74015306fb7e79a00c15370785f309b1ed9aa8d"},
+    {file = "watchfiles-0.19.0.tar.gz", hash = "sha256:d9b073073e048081e502b6c6b0b88714c026a1a4c890569238d04aca5f9ca74b"},
+]
+
+[package.dependencies]
+anyio = ">=3.0.0"
+
+[[package]]
+name = "websockets"
+version = "11.0.3"
+description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)"
+optional = false
+python-versions = ">=3.7"
+files = [
+    {file = "websockets-11.0.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3ccc8a0c387629aec40f2fc9fdcb4b9d5431954f934da3eaf16cdc94f67dbfac"},
+    {file = "websockets-11.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d67ac60a307f760c6e65dad586f556dde58e683fab03323221a4e530ead6f74d"},
+    {file = "websockets-11.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:84d27a4832cc1a0ee07cdcf2b0629a8a72db73f4cf6de6f0904f6661227f256f"},
+    {file = "websockets-11.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffd7dcaf744f25f82190856bc26ed81721508fc5cbf2a330751e135ff1283564"},
+    {file = "websockets-11.0.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7622a89d696fc87af8e8d280d9b421db5133ef5b29d3f7a1ce9f1a7bf7fcfa11"},
+    {file = "websockets-11.0.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bceab846bac555aff6427d060f2fcfff71042dba6f5fca7dc4f75cac815e57ca"},
+    {file = "websockets-11.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:54c6e5b3d3a8936a4ab6870d46bdd6ec500ad62bde9e44462c32d18f1e9a8e54"},
+    {file = "websockets-11.0.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:41f696ba95cd92dc047e46b41b26dd24518384749ed0d99bea0a941ca87404c4"},
+    {file = "websockets-11.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:86d2a77fd490ae3ff6fae1c6ceaecad063d3cc2320b44377efdde79880e11526"},
+    {file = "websockets-11.0.3-cp310-cp310-win32.whl", hash = "sha256:2d903ad4419f5b472de90cd2d40384573b25da71e33519a67797de17ef849b69"},
+    {file = "websockets-11.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:1d2256283fa4b7f4c7d7d3e84dc2ece74d341bce57d5b9bf385df109c2a1a82f"},
+    {file = "websockets-11.0.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e848f46a58b9fcf3d06061d17be388caf70ea5b8cc3466251963c8345e13f7eb"},
+    {file = "websockets-11.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aa5003845cdd21ac0dc6c9bf661c5beddd01116f6eb9eb3c8e272353d45b3288"},
+    {file = "websockets-11.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b58cbf0697721120866820b89f93659abc31c1e876bf20d0b3d03cef14faf84d"},
+    {file = "websockets-11.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:660e2d9068d2bedc0912af508f30bbeb505bbbf9774d98def45f68278cea20d3"},
+    {file = "websockets-11.0.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c1f0524f203e3bd35149f12157438f406eff2e4fb30f71221c8a5eceb3617b6b"},
+    {file = "websockets-11.0.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:def07915168ac8f7853812cc593c71185a16216e9e4fa886358a17ed0fd9fcf6"},
+    {file = "websockets-11.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b30c6590146e53149f04e85a6e4fcae068df4289e31e4aee1fdf56a0dead8f97"},
+    {file = "websockets-11.0.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:619d9f06372b3a42bc29d0cd0354c9bb9fb39c2cbc1a9c5025b4538738dbffaf"},
+    {file = "websockets-11.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:01f5567d9cf6f502d655151645d4e8b72b453413d3819d2b6f1185abc23e82dd"},
+    {file = "websockets-11.0.3-cp311-cp311-win32.whl", hash = "sha256:e1459677e5d12be8bbc7584c35b992eea142911a6236a3278b9b5ce3326f282c"},
+    {file = "websockets-11.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:e7837cb169eca3b3ae94cc5787c4fed99eef74c0ab9506756eea335e0d6f3ed8"},
+    {file = "websockets-11.0.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:9f59a3c656fef341a99e3d63189852be7084c0e54b75734cde571182c087b152"},
+    {file = "websockets-11.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2529338a6ff0eb0b50c7be33dc3d0e456381157a31eefc561771ee431134a97f"},
+    {file = "websockets-11.0.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34fd59a4ac42dff6d4681d8843217137f6bc85ed29722f2f7222bd619d15e95b"},
+    {file = "websockets-11.0.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:332d126167ddddec94597c2365537baf9ff62dfcc9db4266f263d455f2f031cb"},
+    {file = "websockets-11.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6505c1b31274723ccaf5f515c1824a4ad2f0d191cec942666b3d0f3aa4cb4007"},
+    {file = "websockets-11.0.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f467ba0050b7de85016b43f5a22b46383ef004c4f672148a8abf32bc999a87f0"},
+    {file = "websockets-11.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:9d9acd80072abcc98bd2c86c3c9cd4ac2347b5a5a0cae7ed5c0ee5675f86d9af"},
+    {file = "websockets-11.0.3-cp37-cp37m-win32.whl", hash = "sha256:e590228200fcfc7e9109509e4d9125eace2042fd52b595dd22bbc34bb282307f"},
+    {file = "websockets-11.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:b16fff62b45eccb9c7abb18e60e7e446998093cdcb50fed33134b9b6878836de"},
+    {file = "websockets-11.0.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fb06eea71a00a7af0ae6aefbb932fb8a7df3cb390cc217d51a9ad7343de1b8d0"},
+    {file = "websockets-11.0.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8a34e13a62a59c871064dfd8ffb150867e54291e46d4a7cf11d02c94a5275bae"},
+    {file = "websockets-11.0.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4841ed00f1026dfbced6fca7d963c4e7043aa832648671b5138008dc5a8f6d99"},
+    {file = "websockets-11.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a073fc9ab1c8aff37c99f11f1641e16da517770e31a37265d2755282a5d28aa"},
+    {file = "websockets-11.0.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:68b977f21ce443d6d378dbd5ca38621755f2063d6fdb3335bda981d552cfff86"},
+    {file = "websockets-11.0.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1a99a7a71631f0efe727c10edfba09ea6bee4166a6f9c19aafb6c0b5917d09c"},
+    {file = "websockets-11.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:bee9fcb41db2a23bed96c6b6ead6489702c12334ea20a297aa095ce6d31370d0"},
+    {file = "websockets-11.0.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4b253869ea05a5a073ebfdcb5cb3b0266a57c3764cf6fe114e4cd90f4bfa5f5e"},
+    {file = "websockets-11.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:1553cb82942b2a74dd9b15a018dce645d4e68674de2ca31ff13ebc2d9f283788"},
+    {file = "websockets-11.0.3-cp38-cp38-win32.whl", hash = "sha256:f61bdb1df43dc9c131791fbc2355535f9024b9a04398d3bd0684fc16ab07df74"},
+    {file = "websockets-11.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:03aae4edc0b1c68498f41a6772d80ac7c1e33c06c6ffa2ac1c27a07653e79d6f"},
+    {file = "websockets-11.0.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:777354ee16f02f643a4c7f2b3eff8027a33c9861edc691a2003531f5da4f6bc8"},
+    {file = "websockets-11.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8c82f11964f010053e13daafdc7154ce7385ecc538989a354ccc7067fd7028fd"},
+    {file = "websockets-11.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3580dd9c1ad0701169e4d6fc41e878ffe05e6bdcaf3c412f9d559389d0c9e016"},
+    {file = "websockets-11.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f1a3f10f836fab6ca6efa97bb952300b20ae56b409414ca85bff2ad241d2a61"},
+    {file = "websockets-11.0.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df41b9bc27c2c25b486bae7cf42fccdc52ff181c8c387bfd026624a491c2671b"},
+    {file = "websockets-11.0.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:279e5de4671e79a9ac877427f4ac4ce93751b8823f276b681d04b2156713b9dd"},
+    {file = "websockets-11.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1fdf26fa8a6a592f8f9235285b8affa72748dc12e964a5518c6c5e8f916716f7"},
+    {file = "websockets-11.0.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:69269f3a0b472e91125b503d3c0b3566bda26da0a3261c49f0027eb6075086d1"},
+    {file = "websockets-11.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:97b52894d948d2f6ea480171a27122d77af14ced35f62e5c892ca2fae9344311"},
+    {file = "websockets-11.0.3-cp39-cp39-win32.whl", hash = "sha256:c7f3cb904cce8e1be667c7e6fef4516b98d1a6a0635a58a57528d577ac18a128"},
+    {file = "websockets-11.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:c792ea4eabc0159535608fc5658a74d1a81020eb35195dd63214dcf07556f67e"},
+    {file = "websockets-11.0.3-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f2e58f2c36cc52d41f2659e4c0cbf7353e28c8c9e63e30d8c6d3494dc9fdedcf"},
+    {file = "websockets-11.0.3-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de36fe9c02995c7e6ae6efe2e205816f5f00c22fd1fbf343d4d18c3d5ceac2f5"},
+    {file = "websockets-11.0.3-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0ac56b661e60edd453585f4bd68eb6a29ae25b5184fd5ba51e97652580458998"},
+    {file = "websockets-11.0.3-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e052b8467dd07d4943936009f46ae5ce7b908ddcac3fda581656b1b19c083d9b"},
+    {file = "websockets-11.0.3-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:42cc5452a54a8e46a032521d7365da775823e21bfba2895fb7b77633cce031bb"},
+    {file = "websockets-11.0.3-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e6316827e3e79b7b8e7d8e3b08f4e331af91a48e794d5d8b099928b6f0b85f20"},
+    {file = "websockets-11.0.3-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8531fdcad636d82c517b26a448dcfe62f720e1922b33c81ce695d0edb91eb931"},
+    {file = "websockets-11.0.3-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c114e8da9b475739dde229fd3bc6b05a6537a88a578358bc8eb29b4030fac9c9"},
+    {file = "websockets-11.0.3-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e063b1865974611313a3849d43f2c3f5368093691349cf3c7c8f8f75ad7cb280"},
+    {file = "websockets-11.0.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:92b2065d642bf8c0a82d59e59053dd2fdde64d4ed44efe4870fa816c1232647b"},
+    {file = "websockets-11.0.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0ee68fe502f9031f19d495dae2c268830df2760c0524cbac5d759921ba8c8e82"},
+    {file = "websockets-11.0.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcacf2c7a6c3a84e720d1bb2b543c675bf6c40e460300b628bab1b1efc7c034c"},
+    {file = "websockets-11.0.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b67c6f5e5a401fc56394f191f00f9b3811fe843ee93f4a70df3c389d1adf857d"},
+    {file = "websockets-11.0.3-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d5023a4b6a5b183dc838808087033ec5df77580485fc533e7dab2567851b0a4"},
+    {file = "websockets-11.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ed058398f55163a79bb9f06a90ef9ccc063b204bb346c4de78efc5d15abfe602"},
+    {file = "websockets-11.0.3-py3-none-any.whl", hash = "sha256:6681ba9e7f8f3b19440921e99efbb40fc89f26cd71bf539e45d8c8a25c976dc6"},
+    {file = "websockets-11.0.3.tar.gz", hash = "sha256:88fc51d9a26b10fc331be344f1781224a375b78488fc343620184e95a4b27016"},
+]
+
+[[package]]
+name = "werkzeug"
+version = "2.1.2"
+description = "The comprehensive WSGI web application library."
+optional = false
+python-versions = ">=3.7"
+files = [
+    {file = "Werkzeug-2.1.2-py3-none-any.whl", hash = "sha256:72a4b735692dd3135217911cbeaa1be5fa3f62bffb8745c5215420a03dc55255"},
+    {file = "Werkzeug-2.1.2.tar.gz", hash = "sha256:1ce08e8093ed67d638d63879fd1ba3735817f7a80de3674d293f5984f25fb6e6"},
+]
+
+[package.extras]
+watchdog = ["watchdog"]
+
+[[package]]
+name = "wsproto"
+version = "1.2.0"
+description = "WebSockets state-machine based protocol implementation"
+optional = false
+python-versions = ">=3.7.0"
+files = [
+    {file = "wsproto-1.2.0-py3-none-any.whl", hash = "sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736"},
+    {file = "wsproto-1.2.0.tar.gz", hash = "sha256:ad565f26ecb92588a3e43bc3d96164de84cd9902482b130d0ddbaa9664a85065"},
+]
+
+[package.dependencies]
+h11 = ">=0.9.0,<1"
+
+[[package]]
+name = "zipp"
+version = "3.15.0"
+description = "Backport of pathlib-compatible object wrapper for zip files"
+optional = false
+python-versions = ">=3.7"
+files = [
+    {file = "zipp-3.15.0-py3-none-any.whl", hash = "sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556"},
+    {file = "zipp-3.15.0.tar.gz", hash = "sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b"},
+]
+
+[package.extras]
+docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
+testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"]
+
+[metadata]
+lock-version = "2.0"
+python-versions = "^3.9"
+content-hash = "629118cfac10f1dab4c39c6ccd50bd69ca68a7fc05dd2baf1d020082d6b19e4e"
diff --git a/docs/pyproject.toml b/docs/pyproject.toml
new file mode 100644
index 000000000..d2f47c577
--- /dev/null
+++ b/docs/pyproject.toml
@@ -0,0 +1,23 @@
+[tool.poetry]
+name = "docs"
+version = "0.0.0"
+description = "docs"
+authors = ["rmorshea <ryan.morshead@gmail.com>"]
+readme = "README.md"
+
+[tool.poetry.dependencies]
+python = "^3.9"
+reactpy = { path = "../src/py/reactpy", extras = ["starlette", "sanic", "fastapi", "flask", "tornado", "testing"], develop = false }
+furo = "2022.04.07"
+sphinx = "*"
+sphinx-autodoc-typehints = "*"
+sphinx-copybutton = "*"
+sphinx-autobuild = "*"
+sphinx-reredirects = "*"
+sphinx-design = "*"
+sphinx-resolve-py-references = "*"
+sphinxext-opengraph = "*"
+
+[build-system]
+requires = ["poetry-core"]
+build-backend = "poetry.core.masonry.api"
diff --git a/docs/source/_custom_js/package-lock.json b/docs/source/_custom_js/package-lock.json
index fc00fcb83..98cbb7014 100644
--- a/docs/source/_custom_js/package-lock.json
+++ b/docs/source/_custom_js/package-lock.json
@@ -8,7 +8,7 @@
       "name": "reactpy-docs-example-loader",
       "version": "1.0.0",
       "dependencies": {
-        "@reactpy/client": "file:../../../src/client/packages/@reactpy/client"
+        "@reactpy/client": "file:../../../src/js/packages/@reactpy/client"
       },
       "devDependencies": {
         "@rollup/plugin-commonjs": "^21.0.1",
@@ -21,6 +21,7 @@
     "../../../src/client/packages/@reactpy/client": {
       "version": "0.3.1",
       "integrity": "sha512-pIK5eNwFSHKXg7ClpASWFVKyZDYxz59MSFpVaX/OqJFkrJaAxBuhKGXNTMXmuyWOL5Iyvb/ErwwDRxQRzMNkfQ==",
+      "extraneous": true,
       "license": "MIT",
       "dependencies": {
         "event-to-object": "^0.1.2",
@@ -58,8 +59,26 @@
         "react-dom": ">=16 <18"
       }
     },
+    "../../../src/js/packages/@reactpy/client": {
+      "version": "0.3.1",
+      "license": "MIT",
+      "dependencies": {
+        "event-to-object": "^0.1.2",
+        "json-pointer": "^0.6.2"
+      },
+      "devDependencies": {
+        "@types/json-pointer": "^1.0.31",
+        "@types/react": "^17.0",
+        "@types/react-dom": "^17.0",
+        "typescript": "^4.9.5"
+      },
+      "peerDependencies": {
+        "react": ">=16 <18",
+        "react-dom": ">=16 <18"
+      }
+    },
     "node_modules/@reactpy/client": {
-      "resolved": "../../../src/client/packages/@reactpy/client",
+      "resolved": "../../../src/js/packages/@reactpy/client",
       "link": true
     },
     "node_modules/@rollup/plugin-commonjs": {
@@ -434,7 +453,7 @@
   },
   "dependencies": {
     "@reactpy/client": {
-      "version": "file:../../../src/client/packages/@reactpy/client",
+      "version": "file:../../../src/js/packages/@reactpy/client",
       "requires": {
         "@types/json-pointer": "^1.0.31",
         "@types/react": "^17.0",
diff --git a/docs/source/_custom_js/package.json b/docs/source/_custom_js/package.json
index 1d19613f5..78d72b961 100644
--- a/docs/source/_custom_js/package.json
+++ b/docs/source/_custom_js/package.json
@@ -15,6 +15,6 @@
     "rollup": "^2.35.1"
   },
   "dependencies": {
-    "@reactpy/client": "file:../../../src/client/packages/@reactpy/client"
+    "@reactpy/client": "file:../../../src/js/packages/@reactpy/client"
   }
 }
diff --git a/docs/source/_exts/async_doctest.py b/docs/source/_exts/async_doctest.py
index 905c1d00a..96024d488 100644
--- a/docs/source/_exts/async_doctest.py
+++ b/docs/source/_exts/async_doctest.py
@@ -6,7 +6,6 @@
 from sphinx.ext.doctest import DocTestBuilder
 from sphinx.ext.doctest import setup as doctest_setup
 
-
 test_template = """
 import asyncio as __test_template_asyncio
 
@@ -41,10 +40,8 @@ def test_runner(self) -> DocTestRunner:
     @test_runner.setter
     def test_runner(self, value: DocTestRunner) -> None:
         self._test_runner = TestRunnerWrapper(value)
-        return None
 
 
 def setup(app: Sphinx) -> None:
     doctest_setup(app)
     app.add_builder(AsyncDoctestBuilder, override=True)
-    return None
diff --git a/docs/source/_exts/autogen_api_docs.py b/docs/source/_exts/autogen_api_docs.py
index 9f833ac14..b95d85a99 100644
--- a/docs/source/_exts/autogen_api_docs.py
+++ b/docs/source/_exts/autogen_api_docs.py
@@ -1,15 +1,14 @@
 from __future__ import annotations
 
 import sys
+from collections.abc import Collection, Iterator
 from pathlib import Path
-from typing import Collection, Iterator
 
 from sphinx.application import Sphinx
 
-
 HERE = Path(__file__).parent
 SRC = HERE.parent.parent.parent / "src"
-PYTHON_PACKAGE = SRC / "reactpy"
+PYTHON_PACKAGE = SRC / "py" / "reactpy" / "reactpy"
 
 AUTO_DIR = HERE.parent / "_auto"
 AUTO_DIR.mkdir(exist_ok=True)
@@ -82,9 +81,12 @@ def get_module_name(path: Path) -> str:
 
 
 def get_section_symbol(path: Path) -> str:
-    rel_path_parts = path.relative_to(PYTHON_PACKAGE).parts
-    assert len(rel_path_parts) < len(SECTION_SYMBOLS), "package structure is too deep"
-    return SECTION_SYMBOLS[len(rel_path_parts)]
+    rel_path = path.relative_to(PYTHON_PACKAGE)
+    rel_path_parts = rel_path.parts
+    if len(rel_path_parts) > len(SECTION_SYMBOLS):
+        msg = f"package structure is too deep - ran out of section symbols: {rel_path}"
+        raise RuntimeError(msg)
+    return SECTION_SYMBOLS[len(rel_path_parts) - 1]
 
 
 def walk_python_files(root: Path, ignore_dirs: Collection[str]) -> Iterator[Path]:
diff --git a/docs/source/_exts/build_custom_js.py b/docs/source/_exts/build_custom_js.py
index b84378353..97857ba74 100644
--- a/docs/source/_exts/build_custom_js.py
+++ b/docs/source/_exts/build_custom_js.py
@@ -3,11 +3,10 @@
 
 from sphinx.application import Sphinx
 
-
 SOURCE_DIR = Path(__file__).parent.parent
 CUSTOM_JS_DIR = SOURCE_DIR / "_custom_js"
 
 
 def setup(app: Sphinx) -> None:
-    subprocess.run("npm install", cwd=CUSTOM_JS_DIR, shell=True)
-    subprocess.run("npm run build", cwd=CUSTOM_JS_DIR, shell=True)
+    subprocess.run("npm install", cwd=CUSTOM_JS_DIR, shell=True)  # noqa S607
+    subprocess.run("npm run build", cwd=CUSTOM_JS_DIR, shell=True)  # noqa S607
diff --git a/docs/source/_exts/custom_autosectionlabel.py b/docs/source/_exts/custom_autosectionlabel.py
index 9610ad684..92ff5e2df 100644
--- a/docs/source/_exts/custom_autosectionlabel.py
+++ b/docs/source/_exts/custom_autosectionlabel.py
@@ -17,7 +17,6 @@
 from sphinx.util import logging
 from sphinx.util.nodes import clean_astext
 
-
 logger = logging.getLogger(__name__)
 
 
diff --git a/docs/source/_exts/reactpy_example.py b/docs/source/_exts/reactpy_example.py
index 7cad84853..c6b054c07 100644
--- a/docs/source/_exts/reactpy_example.py
+++ b/docs/source/_exts/reactpy_example.py
@@ -4,18 +4,17 @@
 from pathlib import Path
 from typing import Any
 
+from docs_app.examples import (
+    SOURCE_DIR,
+    get_example_files_by_name,
+    get_normalized_example_name,
+)
 from docutils.parsers.rst import directives
 from docutils.statemachine import StringList
 from sphinx.application import Sphinx
 from sphinx.util.docutils import SphinxDirective
 from sphinx_design.tabs import TabSetDirective
 
-from docs.examples import (
-    SOURCE_DIR,
-    get_example_files_by_name,
-    get_normalized_example_name,
-)
-
 
 class WidgetExample(SphinxDirective):
     has_content = False
@@ -41,10 +40,8 @@ def run(self):
         ex_files = get_example_files_by_name(example_name)
         if not ex_files:
             src_file, line_num = self.get_source_info()
-            raise ValueError(
-                f"Missing example named {example_name!r} "
-                f"referenced by document {src_file}:{line_num}"
-            )
+            msg = f"Missing example named {example_name!r} referenced by document {src_file}:{line_num}"
+            raise ValueError(msg)
 
         labeled_tab_items: list[tuple[str, Any]] = []
         if len(ex_files) == 1:
@@ -114,7 +111,8 @@ def _literal_include(path: Path, linenos: bool):
             ".json": "json",
         }[path.suffix]
     except KeyError:
-        raise ValueError(f"Unknown extension type {path.suffix!r}")
+        msg = f"Unknown extension type {path.suffix!r}"
+        raise ValueError(msg) from None
 
     return _literal_include_template.format(
         name=str(path.relative_to(SOURCE_DIR)),
diff --git a/docs/source/_exts/reactpy_view.py b/docs/source/_exts/reactpy_view.py
index 477a6caca..7a2bf85a4 100644
--- a/docs/source/_exts/reactpy_view.py
+++ b/docs/source/_exts/reactpy_view.py
@@ -1,13 +1,14 @@
 import os
+import sys
 
+print(sys.path)
+
+from docs_app.examples import get_normalized_example_name
 from docutils.nodes import raw
 from docutils.parsers.rst import directives
 from sphinx.application import Sphinx
 from sphinx.util.docutils import SphinxDirective
 
-from docs.examples import get_normalized_example_name
-
-
 _REACTPY_EXAMPLE_HOST = os.environ.get("REACTPY_DOC_EXAMPLE_SERVER_HOST", "")
 _REACTPY_STATIC_HOST = os.environ.get("REACTPY_DOC_STATIC_SERVER_HOST", "/docs").rstrip(
     "/"
diff --git a/docs/source/about/contributor-guide.rst b/docs/source/about/contributor-guide.rst
index e26d89e52..b44be9b7e 100644
--- a/docs/source/about/contributor-guide.rst
+++ b/docs/source/about/contributor-guide.rst
@@ -80,12 +80,18 @@ In order to develop ReactPy locally you'll first need to install the following:
     *   - What to Install
         - How to Install
 
+    *   - Python >= 3.9
+        - https://realpython.com/installing-python/
+
+    *   - Hatch
+        - https://hatch.pypa.io/latest/install/
+
+    *   - Poetry
+        - https://python-poetry.org/docs/#installation
+
     *   - Git
         - https://git-scm.com/book/en/v2/Getting-Started-Installing-Git
 
-    *   - Python >= 3.7
-        - https://realpython.com/installing-python/
-
     *   - NodeJS >= 14
         - https://nodejs.org/en/download/package-manager/
 
@@ -106,57 +112,38 @@ Once done, you can clone a local copy of this repository:
     git clone https://github.com/reactive-python/reactpy.git
     cd reactpy
 
-Then, you should be able to run the command below to:
-
-- Install an editable version of the Python code
-
-- Download, build, and install Javascript dependencies
-
-- Install some pre-commit_ hooks for Git
+Then, you should be able to activate your development environment with:
 
 .. code-block:: bash
 
-    pip install -e . -r requirements.txt && pre-commit install
-
-If you modify any Javascript, you'll need to re-install ReactPy:
-
-.. code-block:: bash
-
-    pip install -e .
-
-However you may also ``cd`` to the ``src/client`` directory which contains a
-``package.json`` that you can use to run standard ``npm`` commands from.
+    hatch shell
 
 
 Running The Tests
 -----------------
 
-The test suite for ReactPy is executed with Nox_, which should already be installed if you
-followed the `earlier instructions <Development Environment>`_. The suite covers:
-
-1. Server-side Python code with PyTest_
-
-2. The end-to-end application using Playwright_ in Python
-
-3. Client-side Javascript code with UVU_
-
-Once you've installed them you'll be able to run:
+Tests exist for both Python and Javascript. These can be run with the following:
 
 .. code-block:: bash
 
-    nox -s check-python-tests
+    hatch run test-py
+    hatch run test-js
 
-You can observe the browser as the tests are running by passing an extra flag:
+If you want to run tests for individual packages you'll need to ``cd`` into the
+package directory and run the tests from there. For example, to run the tests just for
+the ``reactpy`` package you'd do:
 
 .. code-block:: bash
 
-    nox -s check-python-tests -- --headed
+    cd src/py/reactpy
+    hatch run test --headed  # run the tests in a browser window
 
-To see a full list of available commands (e.g. ``nox -s <your-command>``) run:
+For Javascript, you'd do:
 
 .. code-block:: bash
 
-    nox -l
+    cd src/js/packages/event-to-object
+    npm run check:tests
 
 
 Code Quality Checks
@@ -172,8 +159,9 @@ The following are currently being used:
 - MyPy_ - a static type checker
 - Black_ - an opinionated code formatter
 - Flake8_ - a style guide enforcement tool
-- ISort_ - a utility for alphabetically sorting imports
+- Ruff_ - An extremely fast Python linter, written in Rust.
 - Prettier_ - a tool for automatically formatting various file types
+- EsLint_ - A Javascript linter
 
 The most strict measure of quality enforced on the codebase is 100% test coverage in
 Python files. This means that every line of coded added to ReactPy requires a test case
@@ -186,10 +174,10 @@ your :ref:`Pull Request <Making a Pull Request>`.
 
 .. note::
 
-    You can manually run ``nox -s format`` to auto format your code without having to
-    do so via ``pre-commit``. However, many IDEs have ways to automatically format upon
-    saving a file
-    (e.g.`VSCode <https://code.visualstudio.com/docs/python/editing#_formatting>`__)
+    You can manually run ``hatch run lint --fix`` to auto format your code without
+    having to do so via ``pre-commit``. However, many IDEs have ways to automatically
+    format upon saving a file (e.g.
+    `VSCode <https://code.visualstudio.com/docs/python/editing#_formatting>`__)
 
 
 Building The Documentation
@@ -199,7 +187,7 @@ To build and display the documentation locally run:
 
 .. code-block:: bash
 
-    nox -s docs
+    hatch run docs
 
 This will compile the documentation from its source files into HTML, start a web server,
 and open a browser to display the now generated documentation. Whenever you change any
@@ -211,14 +199,14 @@ To run some of the examples in the documentation as if they were tests run:
 
 .. code-block:: bash
 
-    nox -s test_docs
+    hatch run test-docs
 
 Building the documentation as it's deployed in production requires Docker_. Once you've
 installed Docker, you can run:
 
 .. code-block:: bash
 
-    nox -s docs_in_docker
+    hatch run docs --docker
 
 Where you can then navigate to http://localhost:5000..
 
@@ -329,7 +317,6 @@ you should refer to their respective documentation in the links below:
 .. _pip: https://pypi.org/project/pip/
 .. _PyTest: pytest <https://docs.pytest.org
 .. _Playwright: https://playwright.dev/python/
-.. _Nox: https://nox.thea.codes/en/stable/#
 .. _React: https://reactjs.org/
 .. _Heroku: https://www.heroku.com/what
 .. _GitHub Actions: https://github.com/features/actions
@@ -338,6 +325,7 @@ you should refer to their respective documentation in the links below:
 .. _MyPy: http://mypy-lang.org/
 .. _Black: https://github.com/psf/black
 .. _Flake8: https://flake8.pycqa.org/en/latest/
-.. _ISort: https://pycqa.github.io/isort/
+.. _Ruff: https://github.com/charliermarsh/ruff
 .. _UVU: https://github.com/lukeed/uvu
 .. _Prettier: https://prettier.io/
+.. _ESLint: https://eslint.org/
diff --git a/docs/source/conf.py b/docs/source/conf.py
index 6c8c50927..08addad8d 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 #
 # Configuration file for the Sphinx documentation builder.
 #
@@ -10,16 +9,14 @@
 from doctest import DONT_ACCEPT_TRUE_FOR_1, ELLIPSIS, NORMALIZE_WHITESPACE
 from pathlib import Path
 
-
 # -- Path Setup --------------------------------------------------------------
 
 THIS_DIR = Path(__file__).parent
 ROOT_DIR = THIS_DIR.parent.parent
-
-# project path
-sys.path.insert(0, str(ROOT_DIR))
+DOCS_DIR = THIS_DIR.parent
 
 # extension path
+sys.path.insert(0, str(DOCS_DIR))
 sys.path.insert(0, str(THIS_DIR / "_exts"))
 
 
@@ -32,7 +29,7 @@
     "a single line of Javascript. It can be run standalone, in a Jupyter Notebook, or "
     "as part of an existing application."
 )
-copyright = "2023, Ryan Morshead"
+copyright = "2023, Ryan Morshead"  # noqa: A001
 author = "Ryan Morshead"
 
 # -- Common External Links ---------------------------------------------------
@@ -40,23 +37,23 @@
 extlinks = {
     "issue": (
         "https://github.com/reactive-python/reactpy/issues/%s",
-        "#",
+        "#%s",
     ),
     "pull": (
         "https://github.com/reactive-python/reactpy/pull/%s",
-        "#",
+        "#%s",
     ),
     "discussion": (
         "https://github.com/reactive-python/reactpy/discussions/%s",
-        "#",
+        "#%s",
     ),
     "discussion-type": (
         "https://github.com/reactive-python/reactpy/discussions/categories/%s",
-        "",
+        "%s",
     ),
     "commit": (
         "https://github.com/reactive-python/reactpy/commit/%s",
-        "",
+        "%s",
     ),
 }
 extlinks_detect_hardcoded_links = True
@@ -320,7 +317,7 @@
 
 # Example configuration for intersphinx: refer to the Python standard library.
 intersphinx_mapping = {
-    "https://docs.python.org/": None,
+    "python": ("https://docs.python.org/3", None),
     "pyalect": ("https://pyalect.readthedocs.io/en/latest", None),
     "sanic": ("https://sanic.readthedocs.io/en/latest/", None),
     "tornado": ("https://www.tornadoweb.org/en/stable/", None),
diff --git a/docs/source/guides/adding-interactivity/components-with-state/_examples/adding_state_variable/main.py b/docs/source/guides/adding-interactivity/components-with-state/_examples/adding_state_variable/main.py
index 860cd093b..a919a2354 100644
--- a/docs/source/guides/adding-interactivity/components-with-state/_examples/adding_state_variable/main.py
+++ b/docs/source/guides/adding-interactivity/components-with-state/_examples/adding_state_variable/main.py
@@ -3,7 +3,6 @@
 
 from reactpy import component, hooks, html, run
 
-
 HERE = Path(__file__)
 DATA_PATH = HERE.parent / "data.json"
 sculpture_data = json.loads(DATA_PATH.read_text())
diff --git a/docs/source/guides/adding-interactivity/components-with-state/_examples/isolated_state/main.py b/docs/source/guides/adding-interactivity/components-with-state/_examples/isolated_state/main.py
index 3149e6396..d07b87140 100644
--- a/docs/source/guides/adding-interactivity/components-with-state/_examples/isolated_state/main.py
+++ b/docs/source/guides/adding-interactivity/components-with-state/_examples/isolated_state/main.py
@@ -3,7 +3,6 @@
 
 from reactpy import component, hooks, html, run
 
-
 HERE = Path(__file__)
 DATA_PATH = HERE.parent / "data.json"
 sculpture_data = json.loads(DATA_PATH.read_text())
diff --git a/docs/source/guides/adding-interactivity/components-with-state/_examples/multiple_state_variables/main.py b/docs/source/guides/adding-interactivity/components-with-state/_examples/multiple_state_variables/main.py
index 3d31b1bfa..87f9651be 100644
--- a/docs/source/guides/adding-interactivity/components-with-state/_examples/multiple_state_variables/main.py
+++ b/docs/source/guides/adding-interactivity/components-with-state/_examples/multiple_state_variables/main.py
@@ -3,7 +3,6 @@
 
 from reactpy import component, hooks, html, run
 
-
 HERE = Path(__file__)
 DATA_PATH = HERE.parent / "data.json"
 sculpture_data = json.loads(DATA_PATH.read_text())
diff --git a/docs/source/guides/adding-interactivity/dangers-of-mutability/_examples/list_re_order.py b/docs/source/guides/adding-interactivity/dangers-of-mutability/_examples/list_re_order.py
index b3bf5a85a..3bd2fd601 100644
--- a/docs/source/guides/adding-interactivity/dangers-of-mutability/_examples/list_re_order.py
+++ b/docs/source/guides/adding-interactivity/dangers-of-mutability/_examples/list_re_order.py
@@ -8,7 +8,7 @@ def ArtistList():
     )
 
     def handle_sort_click(event):
-        set_artists(list(sorted(artists)))
+        set_artists(sorted(artists))
 
     def handle_reverse_click(event):
         set_artists(list(reversed(artists)))
diff --git a/docs/source/guides/creating-interfaces/rendering-data/_examples/sorted_and_filtered_todo_list.py b/docs/source/guides/creating-interfaces/rendering-data/_examples/sorted_and_filtered_todo_list.py
index 111300894..8be2b5f30 100644
--- a/docs/source/guides/creating-interfaces/rendering-data/_examples/sorted_and_filtered_todo_list.py
+++ b/docs/source/guides/creating-interfaces/rendering-data/_examples/sorted_and_filtered_todo_list.py
@@ -6,7 +6,7 @@ def DataList(items, filter_by_priority=None, sort_by_priority=False):
     if filter_by_priority is not None:
         items = [i for i in items if i["priority"] <= filter_by_priority]
     if sort_by_priority:
-        items = list(sorted(items, key=lambda i: i["priority"]))
+        items = sorted(items, key=lambda i: i["priority"])
     list_item_elements = [html.li(i["text"]) for i in items]
     return html.ul(list_item_elements)
 
diff --git a/docs/source/guides/creating-interfaces/rendering-data/_examples/todo_list_with_keys.py b/docs/source/guides/creating-interfaces/rendering-data/_examples/todo_list_with_keys.py
index 26a194d18..8afd2ae55 100644
--- a/docs/source/guides/creating-interfaces/rendering-data/_examples/todo_list_with_keys.py
+++ b/docs/source/guides/creating-interfaces/rendering-data/_examples/todo_list_with_keys.py
@@ -6,7 +6,7 @@ def DataList(items, filter_by_priority=None, sort_by_priority=False):
     if filter_by_priority is not None:
         items = [i for i in items if i["priority"] <= filter_by_priority]
     if sort_by_priority:
-        items = list(sorted(items, key=lambda i: i["priority"]))
+        items = sorted(items, key=lambda i: i["priority"])
     list_item_elements = [html.li({"key": i["id"]}, i["text"]) for i in items]
     return html.ul(list_item_elements)
 
diff --git a/docs/source/guides/escape-hatches/_examples/material_ui_button_no_action.py b/docs/source/guides/escape-hatches/_examples/material_ui_button_no_action.py
index f109326e8..3ad4dac5b 100644
--- a/docs/source/guides/escape-hatches/_examples/material_ui_button_no_action.py
+++ b/docs/source/guides/escape-hatches/_examples/material_ui_button_no_action.py
@@ -1,6 +1,5 @@
 from reactpy import component, run, web
 
-
 mui = web.module_from_template(
     "react@^17.0.0",
     "@material-ui/core@4.12.4",
diff --git a/docs/source/guides/escape-hatches/_examples/material_ui_button_on_click.py b/docs/source/guides/escape-hatches/_examples/material_ui_button_on_click.py
index ad3b13cb2..3fc684005 100644
--- a/docs/source/guides/escape-hatches/_examples/material_ui_button_on_click.py
+++ b/docs/source/guides/escape-hatches/_examples/material_ui_button_on_click.py
@@ -2,7 +2,6 @@
 
 import reactpy
 
-
 mui = reactpy.web.module_from_template(
     "react@^17.0.0",
     "@material-ui/core@4.12.4",
diff --git a/docs/source/guides/escape-hatches/_examples/super_simple_chart/main.py b/docs/source/guides/escape-hatches/_examples/super_simple_chart/main.py
index b9954b0f2..4640785f8 100644
--- a/docs/source/guides/escape-hatches/_examples/super_simple_chart/main.py
+++ b/docs/source/guides/escape-hatches/_examples/super_simple_chart/main.py
@@ -2,7 +2,6 @@
 
 from reactpy import component, run, web
 
-
 file = Path(__file__).parent / "super-simple-chart.js"
 ssc = web.module_from_file("super-simple-chart", file, fallback="⌛")
 SuperSimpleChart = web.export(ssc, "SuperSimpleChart")
diff --git a/docs/source/guides/getting-started/_examples/debug_error_example.py b/docs/source/guides/getting-started/_examples/debug_error_example.py
index 36e5b4910..42d0625b1 100644
--- a/docs/source/guides/getting-started/_examples/debug_error_example.py
+++ b/docs/source/guides/getting-started/_examples/debug_error_example.py
@@ -13,7 +13,8 @@ def GoodComponent():
 
 @component
 def BadComponent():
-    raise RuntimeError("This component raised an error")
+    msg = "This component raised an error"
+    raise RuntimeError(msg)
 
 
 run(App)
diff --git a/docs/source/guides/getting-started/_examples/run_fastapi.py b/docs/source/guides/getting-started/_examples/run_fastapi.py
index ce9974617..bb02e9d6a 100644
--- a/docs/source/guides/getting-started/_examples/run_fastapi.py
+++ b/docs/source/guides/getting-started/_examples/run_fastapi.py
@@ -3,7 +3,6 @@
 from reactpy import run
 from reactpy.backend import fastapi as fastapi_server
 
-
 # the run() function is the entry point for examples
 fastapi_server.configure = lambda _, cmpt: run(cmpt)
 
diff --git a/docs/source/guides/getting-started/_examples/run_flask.py b/docs/source/guides/getting-started/_examples/run_flask.py
index 29503fcc1..f98753784 100644
--- a/docs/source/guides/getting-started/_examples/run_flask.py
+++ b/docs/source/guides/getting-started/_examples/run_flask.py
@@ -3,7 +3,6 @@
 from reactpy import run
 from reactpy.backend import flask as flask_server
 
-
 # the run() function is the entry point for examples
 flask_server.configure = lambda _, cmpt: run(cmpt)
 
diff --git a/docs/source/guides/getting-started/_examples/run_sanic.py b/docs/source/guides/getting-started/_examples/run_sanic.py
index 9e5f14b45..1dae9f6e0 100644
--- a/docs/source/guides/getting-started/_examples/run_sanic.py
+++ b/docs/source/guides/getting-started/_examples/run_sanic.py
@@ -3,7 +3,6 @@
 from reactpy import run
 from reactpy.backend import sanic as sanic_server
 
-
 # the run() function is the entry point for examples
 sanic_server.configure = lambda _, cmpt: run(cmpt)
 
diff --git a/docs/source/guides/getting-started/_examples/run_starlette.py b/docs/source/guides/getting-started/_examples/run_starlette.py
index ffb500f21..966b9ef77 100644
--- a/docs/source/guides/getting-started/_examples/run_starlette.py
+++ b/docs/source/guides/getting-started/_examples/run_starlette.py
@@ -3,7 +3,6 @@
 from reactpy import run
 from reactpy.backend import starlette as starlette_server
 
-
 # the run() function is the entry point for examples
 starlette_server.configure = lambda _, cmpt: run(cmpt)
 
diff --git a/docs/source/guides/getting-started/_examples/run_tornado.py b/docs/source/guides/getting-started/_examples/run_tornado.py
index 03109a1d3..b86126e63 100644
--- a/docs/source/guides/getting-started/_examples/run_tornado.py
+++ b/docs/source/guides/getting-started/_examples/run_tornado.py
@@ -3,7 +3,6 @@
 from reactpy import run
 from reactpy.backend import tornado as tornado_server
 
-
 # the run() function is the entry point for examples
 tornado_server.configure = lambda _, cmpt: run(cmpt)
 
diff --git a/docs/source/guides/getting-started/_examples/sample_app.py b/docs/source/guides/getting-started/_examples/sample_app.py
index b457ca243..a1cc34e6d 100644
--- a/docs/source/guides/getting-started/_examples/sample_app.py
+++ b/docs/source/guides/getting-started/_examples/sample_app.py
@@ -1,4 +1,3 @@
 import reactpy
 
-
 reactpy.run(reactpy.sample.SampleApp)
diff --git a/docs/source/guides/getting-started/_static/embed-reactpy-view/main.py b/docs/source/guides/getting-started/_static/embed-reactpy-view/main.py
index 2ff60ea91..6e3687f27 100644
--- a/docs/source/guides/getting-started/_static/embed-reactpy-view/main.py
+++ b/docs/source/guides/getting-started/_static/embed-reactpy-view/main.py
@@ -4,7 +4,6 @@
 from reactpy import component, html
 from reactpy.backend.sanic import Options, configure
 
-
 app = Sanic("MyApp")
 
 
diff --git a/docs/source/guides/managing-state/sharing-component-state/_examples/filterable_list/main.py b/docs/source/guides/managing-state/sharing-component-state/_examples/filterable_list/main.py
index b07424158..ca68aedcb 100644
--- a/docs/source/guides/managing-state/sharing-component-state/_examples/filterable_list/main.py
+++ b/docs/source/guides/managing-state/sharing-component-state/_examples/filterable_list/main.py
@@ -3,7 +3,6 @@
 
 from reactpy import component, hooks, html, run
 
-
 HERE = Path(__file__)
 DATA_PATH = HERE.parent / "data.json"
 food_data = json.loads(DATA_PATH.read_text())
@@ -33,11 +32,10 @@ def Table(value, set_value):
         name = html.td(row["name"])
         descr = html.td(row["description"])
         tr = html.tr(name, descr, value)
-        if value == "":
+        if not value:
+            rows.append(tr)
+        elif value.lower() in row["name"].lower():
             rows.append(tr)
-        else:
-            if value.lower() in row["name"].lower():
-                rows.append(tr)
         headers = html.tr(html.td(html.b("name")), html.td(html.b("description")))
     table = html.table(html.thead(headers), html.tbody(rows))
     return table
diff --git a/docs/source/reference/_examples/character_movement/main.py b/docs/source/reference/_examples/character_movement/main.py
index 6bdb328e0..9545b0c0a 100644
--- a/docs/source/reference/_examples/character_movement/main.py
+++ b/docs/source/reference/_examples/character_movement/main.py
@@ -4,7 +4,6 @@
 from reactpy import component, html, run, use_state
 from reactpy.widgets import image
 
-
 HERE = Path(__file__)
 CHARACTER_IMAGE = (HERE.parent / "static" / "bunny.png").read_bytes()
 
diff --git a/docs/source/reference/_examples/material_ui_switch.py b/docs/source/reference/_examples/material_ui_switch.py
index ed66d83de..704ae3145 100644
--- a/docs/source/reference/_examples/material_ui_switch.py
+++ b/docs/source/reference/_examples/material_ui_switch.py
@@ -1,6 +1,5 @@
 import reactpy
 
-
 mui = reactpy.web.module_from_template("react", "@material-ui/core@^5.0", fallback="⌛")
 Switch = reactpy.web.export(mui, "Switch")
 
diff --git a/docs/source/reference/_examples/matplotlib_plot.py b/docs/source/reference/_examples/matplotlib_plot.py
index af7a95e67..5c4d616fe 100644
--- a/docs/source/reference/_examples/matplotlib_plot.py
+++ b/docs/source/reference/_examples/matplotlib_plot.py
@@ -10,7 +10,7 @@
 def PolynomialPlot():
     coefficients, set_coefficients = reactpy.hooks.use_state([0])
 
-    x = [n for n in linspace(-1, 1, 50)]
+    x = list(linspace(-1, 1, 50))
     y = [polynomial(value, coefficients) for value in x]
 
     return reactpy.html.div(
@@ -31,7 +31,7 @@ def set_value_at_index(event, index=i):
         inputs.append(poly_coef_input(i + 1, set_value_at_index))
 
     def add_input():
-        set_values(values + [0])
+        set_values([*values, 0])
 
     def del_input():
         set_values(values[:-1])
@@ -62,7 +62,7 @@ def poly_coef_input(index, callback):
         reactpy.html.label(
             "C",
             reactpy.html.sub(index),
-            " × X",
+            " x X",
             reactpy.html.sup(index),
         ),
         reactpy.html.input({"type": "number", "on_change": callback}),
diff --git a/docs/source/reference/_examples/network_graph.py b/docs/source/reference/_examples/network_graph.py
index f31bd96b0..79b1092f3 100644
--- a/docs/source/reference/_examples/network_graph.py
+++ b/docs/source/reference/_examples/network_graph.py
@@ -2,7 +2,6 @@
 
 import reactpy
 
-
 react_cytoscapejs = reactpy.web.module_from_template(
     "react",
     "react-cytoscapejs",
diff --git a/docs/source/reference/_examples/pigeon_maps.py b/docs/source/reference/_examples/pigeon_maps.py
index 5241a0259..1ddf04fdc 100644
--- a/docs/source/reference/_examples/pigeon_maps.py
+++ b/docs/source/reference/_examples/pigeon_maps.py
@@ -1,6 +1,5 @@
 import reactpy
 
-
 pigeon_maps = reactpy.web.module_from_template("react", "pigeon-maps", fallback="⌛")
 Map, Marker = reactpy.web.export(pigeon_maps, ["Map", "Marker"])
 
@@ -9,18 +8,16 @@
 def MapWithMarkers():
     marker_anchor, add_marker_anchor, remove_marker_anchor = use_set()
 
-    markers = list(
-        map(
-            lambda anchor: Marker(
-                {
-                    "anchor": anchor,
-                    "onClick": lambda: remove_marker_anchor(anchor),
-                },
-                key=str(anchor),
-            ),
-            marker_anchor,
+    markers = [
+        Marker(
+            {
+                "anchor": anchor,
+                "onClick": lambda event, a=anchor: remove_marker_anchor(a),
+            },
+            key=str(anchor),
         )
-    )
+        for anchor in marker_anchor
+    ]
 
     return Map(
         {
diff --git a/docs/source/reference/_examples/simple_dashboard.py b/docs/source/reference/_examples/simple_dashboard.py
index 3d592f775..66913fc84 100644
--- a/docs/source/reference/_examples/simple_dashboard.py
+++ b/docs/source/reference/_examples/simple_dashboard.py
@@ -5,7 +5,6 @@
 import reactpy
 from reactpy.widgets import Input
 
-
 victory = reactpy.web.module_from_template(
     "react",
     "victory-line",
diff --git a/docs/source/reference/_examples/snake_game.py b/docs/source/reference/_examples/snake_game.py
index eecfea173..36916410e 100644
--- a/docs/source/reference/_examples/snake_game.py
+++ b/docs/source/reference/_examples/snake_game.py
@@ -107,7 +107,7 @@ async def animate():
 
         if snake[-1] == food:
             set_food()
-            new_snake = snake + [new_snake_head]
+            new_snake = [*snake, new_snake_head]
         else:
             new_snake = snake[1:] + [new_snake_head]
 
diff --git a/docs/source/reference/_examples/todo.py b/docs/source/reference/_examples/todo.py
index f688f57be..104ea59a9 100644
--- a/docs/source/reference/_examples/todo.py
+++ b/docs/source/reference/_examples/todo.py
@@ -7,7 +7,7 @@ def Todo():
 
     async def add_new_task(event):
         if event["key"] == "Enter":
-            set_items(items + [event["target"]["value"]])
+            set_items([*items, event["target"]["value"]])
 
     tasks = []
 
diff --git a/docs/source/reference/_examples/use_reducer_counter.py b/docs/source/reference/_examples/use_reducer_counter.py
index 44009422c..6f9581dfd 100644
--- a/docs/source/reference/_examples/use_reducer_counter.py
+++ b/docs/source/reference/_examples/use_reducer_counter.py
@@ -9,7 +9,8 @@ def reducer(count, action):
     elif action == "reset":
         return 0
     else:
-        raise ValueError(f"Unknown action '{action}'")
+        msg = f"Unknown action '{action}'"
+        raise ValueError(msg)
 
 
 @reactpy.component
diff --git a/docs/source/reference/_examples/victory_chart.py b/docs/source/reference/_examples/victory_chart.py
index 43bf2dbde..ce37c522f 100644
--- a/docs/source/reference/_examples/victory_chart.py
+++ b/docs/source/reference/_examples/victory_chart.py
@@ -1,6 +1,5 @@
 import reactpy
 
-
 victory = reactpy.web.module_from_template("react", "victory-bar", fallback="⌛")
 VictoryBar = reactpy.web.export(victory, "VictoryBar")
 
diff --git a/noxfile.py b/noxfile.py
deleted file mode 100644
index 35973db2f..000000000
--- a/noxfile.py
+++ /dev/null
@@ -1,553 +0,0 @@
-from __future__ import annotations
-
-import json
-import os
-import re
-from argparse import REMAINDER
-from dataclasses import replace
-from pathlib import Path
-from shutil import rmtree
-from typing import TYPE_CHECKING, Callable, NamedTuple, Sequence, cast
-
-from noxopt import Annotated, NoxOpt, Option, Session
-
-
-# --- Typing Preamble ------------------------------------------------------------------
-
-
-if TYPE_CHECKING:
-    # not available in typing module until Python 3.8
-    # not available in typing module until Python 3.10
-    from typing import Literal, Protocol, TypeAlias
-
-    class ReleasePrepFunc(Protocol):
-        def __call__(
-            self, session: Session, package: PackageInfo
-        ) -> Callable[[bool], None]:
-            ...
-
-
-LanguageName: TypeAlias = "Literal['py', 'js']"
-
-
-# --- Constants ------------------------------------------------------------------------
-
-
-ROOT_DIR = Path(__file__).parent.resolve()
-SRC_DIR = ROOT_DIR / "src"
-CLIENT_DIR = SRC_DIR / "client"
-REACTPY_DIR = SRC_DIR / "reactpy"
-TAG_PATTERN = re.compile(
-    # start
-    r"^"
-    # package name
-    r"(?P<name>[0-9a-zA-Z-@/]+)-"
-    # package version
-    r"v(?P<version>[0-9][0-9a-zA-Z-\.\+]*)"
-    # end
-    r"$"
-)
-REMAINING_ARGS = Option(nargs=REMAINDER, type=str)
-
-
-# --- Session Setup --------------------------------------------------------------------
-
-
-group = NoxOpt(auto_tag=True)
-
-
-@group.setup
-def setup_checks(session: Session) -> None:
-    session.install("--upgrade", "pip")
-    session.run("pip", "--version")
-    session.run("npm", "--version", external=True)
-
-
-@group.setup("check-javascript")
-def setup_javascript_checks(session: Session) -> None:
-    session.chdir(CLIENT_DIR)
-    session.run("npm", "ci", external=True)
-
-
-# --- Session Definitions --------------------------------------------------------------
-
-
-@group.session
-def format(session: Session) -> None:
-    """Auto format Python and Javascript code"""
-    # format Python
-    install_requirements_file(session, "check-style")
-    session.run("black", ".")
-    session.run("isort", ".")
-
-    # format client Javascript
-    session.chdir(CLIENT_DIR)
-    session.run("npm", "run", "format", external=True)
-
-    # format docs Javascript
-    session.chdir(ROOT_DIR / "docs" / "source" / "_custom_js")
-    session.run("npm", "run", "format", external=True)
-
-
-@group.session
-def tsc(session: Session) -> None:
-    session.chdir(CLIENT_DIR)
-    session.run("npx", "tsc", "-b", "-w", "packages/app", external=True)
-
-
-@group.session
-def example(session: Session) -> None:
-    """Run an example"""
-    session.install("matplotlib")
-    install_reactpy_dev(session)
-    session.run(
-        "python",
-        "scripts/one_example.py",
-        *session.posargs,
-        env=get_reactpy_script_env(),
-    )
-
-
-@group.session
-def docs(session: Session) -> None:
-    """Build and display documentation in the browser (automatically reloads on change)"""
-    install_requirements_file(session, "build-docs")
-    install_reactpy_dev(session)
-    session.run(
-        "python",
-        "scripts/live_docs.py",
-        "--open-browser",
-        # watch python source too
-        "--watch=src/reactpy",
-        # for some reason this matches absolute paths
-        "--ignore=**/_auto/*",
-        "--ignore=**/_static/custom.js",
-        "--ignore=**/node_modules/*",
-        "--ignore=**/package-lock.json",
-        "-a",
-        "-E",
-        "-b",
-        "html",
-        "docs/source",
-        "docs/build",
-        env={**os.environ, **get_reactpy_script_env()},
-    )
-
-
-@group.session
-def docs_in_docker(session: Session) -> None:
-    """Build a docker image for the documentation and run it to mimic production"""
-    session.run(
-        "docker",
-        "build",
-        ".",
-        "--file",
-        "docs/Dockerfile",
-        "--tag",
-        "reactpy-docs:latest",
-        external=True,
-    )
-    session.run(
-        "docker",
-        "run",
-        "-it",
-        "-p",
-        "5000:5000",
-        "-e",
-        "DEBUG=1",
-        "--rm",
-        "reactpy-docs:latest",
-        external=True,
-    )
-
-
-@group.session
-def check_python_tests(
-    session: Session,
-    no_cov: Annotated[bool, Option(help="turn off coverage checks")] = False,
-    headed: Annotated[bool, Option(help="run tests with a headed browser")] = False,
-    pytest: Annotated[Sequence[str], replace(REMAINING_ARGS, help="pytest args")] = (),
-) -> None:
-    """Run the Python-based test suite"""
-    session.env["REACTPY_DEBUG_MODE"] = "1"
-    install_requirements_file(session, "test-env")
-    session.run("playwright", "install", "chromium")
-
-    args = ["pytest", *pytest]
-    if headed:
-        args.append("--headed")
-
-    if no_cov:
-        session.log("Coverage won't be checked")
-        session.install(".[all]")
-    else:
-        args = ["coverage", "run", "--source=src/reactpy", "--module", *args]
-        install_reactpy_dev(session)
-
-    session.run(*args)
-
-    if not no_cov:
-        session.run("coverage", "report")
-
-
-@group.session
-def check_python_types(session: Session) -> None:
-    """Perform a static type analysis of the Python codebase"""
-    install_requirements_file(session, "check-types")
-    install_requirements_file(session, "pkg-deps")
-    install_requirements_file(session, "pkg-extras")
-    session.run("mypy", "--version")
-    session.run("mypy", "--show-error-codes", "--strict", "src/reactpy")
-    session.run("mypy", "--show-error-codes", "noxfile.py")
-
-
-@group.session
-def check_python_format(session: Session) -> None:
-    """Check that Python style guidelines are being followed"""
-    install_requirements_file(session, "check-style")
-    session.run("flake8", "src/reactpy", "tests", "docs")
-    session.run("black", ".", "--check")
-    session.run("isort", ".", "--check-only")
-
-
-@group.session
-def check_python_build(session: Session) -> None:
-    """Test whether the Python package can be build for distribution"""
-    install_requirements_file(session, "build-pkg")
-    session.run("python", "-m", "build", "--sdist", "--wheel", "--outdir", "dist", ".")
-
-
-@group.session
-def check_docs(session: Session) -> None:
-    """Verify that the docs build and that doctests pass"""
-    install_requirements_file(session, "build-docs")
-    install_reactpy_dev(session)
-    session.run(
-        "sphinx-build",
-        "-a",  # re-write all output files
-        "-T",  # show full tracebacks
-        "-W",  # turn warnings into errors
-        "--keep-going",  # complete the build, but still report warnings as errors
-        "-b",
-        "html",
-        "docs/source",
-        "docs/build",
-    )
-    session.run("sphinx-build", "-b", "doctest", "docs/source", "docs/build")
-    # ensure docker image build works too
-    session.run("docker", "build", ".", "--file", "docs/Dockerfile", external=True)
-
-
-@group.session
-def check_javascript_tests(session: Session) -> None:
-    session.run("npm", "run", "check:tests", external=True)
-
-
-@group.session
-def check_javascript_format(session: Session) -> None:
-    session.run("npm", "run", "check:format", external=True)
-
-
-@group.session
-def check_javascript_types(session: Session) -> None:
-    session.run("npm", "run", "build", external=True)
-    session.run("npm", "run", "check:types", external=True)
-
-
-@group.session
-def check_javascript_build(session: Session) -> None:
-    session.run("npm", "run", "build", external=True)
-
-
-@group.session
-def build_javascript(session: Session) -> None:
-    """Build javascript client code"""
-    session.chdir(CLIENT_DIR)
-    session.run("npm", "run", "build", external=True)
-
-
-@group.session
-def build_python(session: Session) -> None:
-    """Build python package dist"""
-    rmtree(str(ROOT_DIR / "build"))
-    rmtree(str(ROOT_DIR / "dist"))
-    install_requirements_file(session, "build-pkg")
-    session.run("python", "-m", "build", "--sdist", "--wheel", "--outdir", "dist", ".")
-
-
-@group.session
-def publish(
-    session: Session,
-    publish_dry_run: Annotated[
-        bool,
-        Option(help="whether to test the release process"),
-    ] = False,
-    publish_fake_tags: Annotated[
-        Sequence[str],
-        Option(nargs="*", type=str, help="fake tags to use for a dry run release"),
-    ] = (),
-) -> None:
-    packages = get_packages(session)
-
-    release_prep: dict[LanguageName, ReleasePrepFunc] = {
-        "js": prepare_javascript_release,
-        "py": prepare_python_release,
-    }
-
-    if publish_fake_tags and not publish_dry_run:
-        session.error("Cannot specify --publish-fake-tags without --publish-dry-run")
-
-    parsed_tags: list[TagInfo] = []
-    for tag in publish_fake_tags or get_current_tags(session):
-        tag_info = parse_tag(tag)
-        if tag_info is None:
-            session.error(
-                f"Invalid tag {tag} - must be of the form <package>-<language>-<version>"
-            )
-        parsed_tags.append(tag_info)  # type: ignore
-
-    publishers: list[tuple[Path, Callable[[bool], None]]] = []
-    for tag, tag_pkg, tag_ver in parsed_tags:
-        if tag_pkg not in packages:
-            session.error(f"Tag {tag} references package {tag_pkg} that does not exist")
-
-        pkg_name, pkg_path, pkg_lang, pkg_ver = pkg_info = packages[tag_pkg]
-        if pkg_ver != tag_ver:
-            session.error(
-                f"Tag {tag} references version {tag_ver} of package {tag_pkg}, "
-                f"but the current version is {pkg_ver}"
-            )
-
-        session.chdir(pkg_path)
-        session.log(f"Preparing {tag_pkg} for release...")
-        publishers.append((pkg_path, release_prep[pkg_lang](session, pkg_info)))
-
-    for pkg_path, publish in publishers:
-        session.log(f"Publishing {pkg_path}...")
-        session.chdir(pkg_path)
-        publish(publish_dry_run)
-
-
-# --- Utilities ------------------------------------------------------------------------
-
-
-def install_requirements_file(session: Session, name: str) -> None:
-    file_path = ROOT_DIR / "requirements" / (name + ".txt")
-    assert file_path.exists(), f"requirements file {file_path} does not exist"
-    session.install("-r", str(file_path))
-
-
-def install_reactpy_dev(session: Session, extras: str = "all") -> None:
-    if "--no-install" not in session.posargs:
-        session.install("-e", f".[{extras}]")
-    else:
-        session.posargs.remove("--no-install")
-
-
-def get_reactpy_script_env() -> dict[str, str]:
-    return {
-        "PYTHONPATH": os.getcwd(),
-        "REACTPY_DEBUG_MODE": os.environ.get("REACTPY_DEBUG_MODE", "1"),
-        "REACTPY_TESTING_DEFAULT_TIMEOUT": os.environ.get(
-            "REACTPY_TESTING_DEFAULT_TIMEOUT", "6.0"
-        ),
-        "REACTPY_CHECK_VDOM_SPEC": os.environ.get("REACTPY_CHECK_VDOM_SPEC", "0"),
-    }
-
-
-def prepare_javascript_release(
-    session: Session, package: PackageInfo
-) -> Callable[[bool], None]:
-    node_auth_token = session.env.get("NODE_AUTH_TOKEN")
-    if node_auth_token is None:
-        session.error("NODE_AUTH_TOKEN environment variable must be set")
-
-    session.run("npm", "ci", external=True)
-    session.run("npm", "run", "build", external=True)
-
-    def publish(dry_run: bool) -> None:
-        if dry_run:
-            session.run(
-                "npm",
-                "--workspace",
-                package.name,
-                "pack",
-                "--dry-run",
-                external=True,
-            )
-            return
-        session.run(
-            "npm",
-            "--workspace",
-            package.name,
-            "publish",
-            "--access",
-            "public",
-            external=True,
-            env={"NODE_AUTH_TOKEN": node_auth_token},
-        )
-
-    return publish
-
-
-def prepare_python_release(
-    session: Session, package: PackageInfo
-) -> Callable[[bool], None]:
-    twine_username = session.env.get("PYPI_USERNAME")
-    twine_password = session.env.get("PYPI_PASSWORD")
-
-    if not (twine_password and twine_username):
-        session.error(
-            "PYPI_USERNAME and PYPI_PASSWORD environment variables must be set"
-        )
-
-    for build_dir_name in ["build", "dist"]:
-        build_dir_path = Path.cwd() / build_dir_name
-        if build_dir_path.exists():
-            rmtree(str(build_dir_path))
-
-    install_requirements_file(session, "build-pkg")
-    session.run("python", "-m", "build", "--sdist", "--wheel", "--outdir", "dist", ".")
-
-    def publish(dry_run: bool):
-        if dry_run:
-            session.run("twine", "check", "dist/*")
-            return
-
-        session.run(
-            "twine",
-            "upload",
-            "dist/*",
-            env={"TWINE_USERNAME": twine_username, "TWINE_PASSWORD": twine_password},
-        )
-
-    return publish
-
-
-def get_packages(session: Session) -> dict[str, PackageInfo]:
-    packages: dict[str, PackageInfo] = {
-        "reactpy": PackageInfo(
-            "reactpy", ROOT_DIR, "py", get_reactpy_package_version(session)
-        )
-    }
-
-    # collect javascript packages
-    js_package_paths: list[Path] = []
-    for maybed_pkg in (CLIENT_DIR / "packages").glob("*"):
-        if not (maybed_pkg / "package.json").exists():
-            for nmaybe_namespaced_pkg in maybed_pkg.glob("*"):
-                if (nmaybe_namespaced_pkg / "package.json").exists():
-                    js_package_paths.append(nmaybe_namespaced_pkg)
-        else:
-            js_package_paths.append(maybed_pkg)
-
-    # get javascript package info
-    for pkg in js_package_paths:
-        pkg_json_file = pkg / "package.json"  # we already know this exists
-
-        pkg_json = json.loads(pkg_json_file.read_text())
-
-        pkg_name = pkg_json.get("name")
-        pkg_version = pkg_json.get("version")
-
-        if pkg_version is None or pkg_name is None:
-            session.log(f"Skipping - {pkg_name} has no name/version in package.json")
-            continue
-
-        if pkg_name in packages:
-            session.error(f"Duplicate package name {pkg_name}")
-
-        packages[pkg_name] = PackageInfo(pkg_name, CLIENT_DIR, "js", pkg_version)
-
-    return packages
-
-
-class PackageInfo(NamedTuple):
-    name: str
-    path: Path
-    language: LanguageName
-    version: str
-
-
-def get_current_tags(session: Session) -> list[str]:
-    """Get tags for the current commit"""
-    # check if unstaged changes
-    try:
-        session.run(
-            "git",
-            "diff",
-            "--cached",
-            "--exit-code",
-            silent=True,
-            external=True,
-        )
-        session.run(
-            "git",
-            "diff",
-            "--exit-code",
-            silent=True,
-            external=True,
-        )
-    except Exception:
-        session.error("Cannot create a tag - there are uncommited changes")
-
-    tags_per_commit: dict[str, list[str]] = {}
-    for commit, tag in map(
-        str.split,
-        cast(
-            str,
-            session.run(
-                "git",
-                "for-each-ref",
-                "--format",
-                r"%(objectname) %(refname:short)",
-                "refs/tags",
-                silent=True,
-                external=True,
-            ),
-        ).splitlines(),
-    ):
-        tags_per_commit.setdefault(commit, []).append(tag)
-
-    current_commit = cast(
-        str, session.run("git", "rev-parse", "HEAD", silent=True, external=True)
-    ).strip()
-    tags = tags_per_commit.get(current_commit, [])
-
-    if not tags:
-        session.error("No tags found for current commit")
-
-    session.log(f"Found tags: {tags}")
-
-    return tags
-
-
-def parse_tag(tag: str) -> TagInfo | None:
-    match = TAG_PATTERN.match(tag)
-    if not match:
-        return None
-    return TagInfo(tag, match["name"], match["version"])
-
-
-class TagInfo(NamedTuple):
-    tag: str
-    package: str
-    version: str
-
-
-def get_reactpy_package_version(session: Session) -> str:  # type: ignore[return]
-    pkg_root_init_file = REACTPY_DIR / "__init__.py"
-    for line in pkg_root_init_file.read_text().split("\n"):
-        if line.startswith('__version__ = "') and line.endswith('"  # DO NOT MODIFY'):
-            return (
-                line
-                # get assignment value
-                .split("=", 1)[1]
-                # remove "DO NOT MODIFY" comment
-                .split("#", 1)[0]
-                # clean up leading/trailing space
-                .strip()
-                # remove the quotes
-                [1:-1]
-            )
-    session.error(f"No version found in {pkg_root_init_file}")
diff --git a/pyproject.toml b/pyproject.toml
index f843140ea..c2845ea6f 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,61 +1,132 @@
-[build-system]
-requires = ["setuptools>=42", "wheel"]
-build-backend = "setuptools.build_meta"
-
-[tool.isort]
-multi_line_output = 3
-force_grid_wrap = 0
-use_parentheses = "True"
-ensure_newline_before_comments = "True"
-include_trailing_comma = "True"
-line_length = 88
-lines_after_imports = 2
-
-[tool.mypy]
-incremental = false
-ignore_missing_imports = true
-warn_unused_configs = true
-warn_redundant_casts = true
-warn_unused_ignores = true
-
-[tool.pytest.ini_options]
-testpaths = "tests"
-xfail_strict = true
-markers = ["slow: marks tests as slow (deselect with '-m \"not slow\"')"]
-python_files = "*asserts.py test_*.py"
-asyncio_mode = "auto"
-
-[tool.coverage.report]
-fail_under = 100
-show_missing = true
-skip_covered = true
-sort = "Name"
-exclude_lines = [
-    # These are regex patterns
-    'pragma: no cover',
-    '\.\.\.',
-    'raise NotImplementedError',
-    'if TYPE_CHECKING[\s:]',
-]
-omit = [
-    "src/reactpy/__main__.py",
+# --- Project ----------------------------------------------------------------------------
+
+[project]
+name = "scripts"
+version = "0.0.0"
+description = "Scripts for managing the ReactPy respository"
+
+# --- Hatch ----------------------------------------------------------------------------
+
+[tool.hatch.envs.default]
+detached = true
+dependencies = [
+  "invoke",
+  # lint
+  "black",
+  "ruff",
+  "toml",
+  "flake8",
+  "flake8-pyproject",
+  "reactpy-flake8 >=0.7",
+  # publish
+  "semver >=2, <3",
+  "twine",
 ]
 
-[tool.pydocstyle]
-inherit = false
-match = '.*\.py'
-convention = "google"
-add_ignore = ["D100", "D101", "D102", "D103", "D104", "D105", "D107", "D412", "D415"]
+[tool.hatch.envs.default.scripts]
+publish = "invoke publish {args}"
+docs = "invoke docs {args}"
+
+lint-py = "invoke lint-py {args}"
+lint-js = "invoke lint-js {args}"
+
+test-py = "invoke test-py {args}"
+test-js = "invoke test-js"
+test-docs = "invoke test-docs"
+
+# --- Black ----------------------------------------------------------------------------
+
+[tool.black]
+target-version = ["py39"]
+line-length = 88
+
+# --- Flake8 ----------------------------------------------------------------------------
 
 [tool.flake8]
-ignore = ["E203", "E266", "E501", "W503", "F811", "N802", "N806"]
-per-file-ignores = [
-    # sometimes this is required in order to hide setup for an example
-    "docs/*/_examples/*.py:E402",
+select = ["RPY"]  # only need to check with reactpy-flake8
+exclude = ["**/node_modules/*", ".eggs/*", ".tox/*", "**/venv/*"]
+
+# --- Ruff -----------------------------------------------------------------------------
+
+[tool.ruff]
+target-version = "py39"
+line-length = 88
+select = [
+  "A",
+  "ARG",
+  "B",
+  "C",
+  "DTZ",
+  "E",
+  # error message linting is overkill
+  # "EM",
+  "F",
+  # TODO: turn this on later
+  # "FBT",
+  "I",
+  "ICN",
+  "ISC",
+  "N",
+  "PLC",
+  "PLE",
+  "PLR",
+  "PLW",
+  "Q",
+  "RUF",
+  "S",
+  "T",
+  "TID",
+  "UP",
+  "W",
+  "YTT",
+]
+ignore = [
+  # TODO: turn this on later
+  "N802", "N806",  # allow TitleCase functions/variables
+  # We're not any cryptography
+  "S311",
+  # For loop variable re-assignment seems like an uncommon mistake
+  "PLW2901",
+  # Let Black deal with line-length
+  "E501",
+  # Allow args/attrs to shadow built-ins
+  "A002", "A003",
+  # Allow unused args (useful for documenting what the parameter is for later)
+  "ARG001", "ARG002", "ARG005",
+  # Allow non-abstract empty methods in abstract base classes
+  "B027",
+  # Allow boolean positional values in function calls, like `dict.get(... True)`
+  "FBT003",
+  # If we're making an explicit comparison to a falsy value it was probably intentional
+  "PLC1901",
+  # Ignore checks for possible passwords
+  "S105", "S106", "S107",
+  # Ignore complexity
+  "C901", "PLR0911", "PLR0912", "PLR0913", "PLR0915",
+]
+unfixable = [
+  # Don't touch unused imports
+  "F401",
+]
+
+[tool.ruff.isort]
+known-first-party = ["reactpy"]
+
+[tool.ruff.flake8-tidy-imports]
+ban-relative-imports = "all"
+
+[tool.ruff.per-file-ignores]
+# Tests can use magic values, assertions, and relative imports
+"**/tests/**/*" = ["PLR2004", "S101", "TID252"]
+"docs/**/*.py" = [
+  # Examples require some extra setup before import
+  "E402",
+  # Allow exec
+  "S102",
+  # Allow print
+  "T201",
+]
+"scripts/**/*.py" = [
+  # Allow print
+  "T201",
 ]
-max-line-length = 88
-max-complexity = 20
-select = ["B", "C", "E", "F", "W", "T4", "B9", "N", "RPY"]
-exclude = ["**/node_modules/*", ".eggs/*", ".tox/*"]
-# -- flake8-tidy-imports --
-ban-relative-imports = "true"
diff --git a/requirements/build-docs.txt b/requirements/build-docs.txt
deleted file mode 100644
index 274cbb56b..000000000
--- a/requirements/build-docs.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-sphinx
-sphinx-autodoc-typehints
-furo ==2022.04.07
-sphinx-copybutton
-sphinx-autobuild
-sphinx-reredirects
-sphinx-design
-sphinx-resolve-py-references
-sphinxext-opengraph
diff --git a/requirements/build-pkg.txt b/requirements/build-pkg.txt
deleted file mode 100644
index 82f40eafa..000000000
--- a/requirements/build-pkg.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-twine
-wheel
-build
diff --git a/requirements/check-style.txt b/requirements/check-style.txt
deleted file mode 100644
index 035e52138..000000000
--- a/requirements/check-style.txt
+++ /dev/null
@@ -1,10 +0,0 @@
-black[jupyter]
-flake8
-flake8-pyproject
-reactpy-flake8 >=0.7
-flake8-print
-flake8-tidy-imports
-isort >=5.7.0
-pep8-naming
-# pydocstyle
-git+https://github.com/PyCQA/pydocstyle.git@bd4993345a241bd0d5128f21de56394b1cde3714#egg=pydocstyle
diff --git a/requirements/check-types.txt b/requirements/check-types.txt
deleted file mode 100644
index 2ae26d60a..000000000
--- a/requirements/check-types.txt
+++ /dev/null
@@ -1,6 +0,0 @@
-mypy
-types-click
-types-tornado
-types-pkg-resources
-types-flask
-types-requests
diff --git a/requirements/make-release.txt b/requirements/make-release.txt
deleted file mode 100644
index da7b791ef..000000000
--- a/requirements/make-release.txt
+++ /dev/null
@@ -1 +0,0 @@
-semver >=2, <3
diff --git a/requirements/nox-deps.txt b/requirements/nox-deps.txt
deleted file mode 100644
index 218b48093..000000000
--- a/requirements/nox-deps.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-nox
-noxopt
diff --git a/requirements/pkg-deps.txt b/requirements/pkg-deps.txt
deleted file mode 100644
index 885fa4828..000000000
--- a/requirements/pkg-deps.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-typing-extensions >=3.10
-mypy-extensions >=0.4.3
-anyio >=3
-jsonpatch >=1.32
-fastjsonschema >=2.14.5
-requests >=2
-colorlog >=6
-asgiref >=3
-lxml >=4
diff --git a/requirements/pkg-extras.txt b/requirements/pkg-extras.txt
deleted file mode 100644
index 70edde6d5..000000000
--- a/requirements/pkg-extras.txt
+++ /dev/null
@@ -1,24 +0,0 @@
-# extra=starlette
-starlette >=0.13.6
-uvicorn[standard] >=0.19.0
-
-# extra=sanic
-sanic >=21
-sanic-cors
-uvicorn[standard] >=0.19.0
-
-# extra=fastapi
-fastapi >=0.63.0
-uvicorn[standard] >=0.19.0
-
-# extra=flask
-flask
-markupsafe>=1.1.1,<2.1
-flask-cors
-flask-sock
-
-# extra=tornado
-tornado
-
-# extra=testing
-playwright
diff --git a/requirements/test-env.txt b/requirements/test-env.txt
deleted file mode 100644
index ac59ea45b..000000000
--- a/requirements/test-env.txt
+++ /dev/null
@@ -1,15 +0,0 @@
-pytest
-pytest-asyncio>=0.17
-pytest-mock
-pytest-rerunfailures
-pytest-timeout
-
-coverage
-responses
-playwright
-
-# I'm not quite sure why this needs to be installed for tests with Sanic to pass
-sanic-testing
-
-# Used to generate model changes from layout update messages
-jsonpointer
diff --git a/scripts/README.md b/scripts/README.md
deleted file mode 100644
index a9e8fa35c..000000000
--- a/scripts/README.md
+++ /dev/null
@@ -1,5 +0,0 @@
-# Scripts
-
-All scripts should be run from the repository root (typically using
-[`nox`](https://nox.thea.codes/en/stable/)). Use `nox --list` to see the list of
-available scripts.
diff --git a/scripts/one_example.py b/scripts/one_example.py
deleted file mode 100644
index 079bd7a6a..000000000
--- a/scripts/one_example.py
+++ /dev/null
@@ -1,103 +0,0 @@
-import sys
-import time
-from os.path import getmtime
-from threading import Event, Thread
-
-import reactpy
-from docs.examples import all_example_names, get_example_files_by_name, load_one_example
-from reactpy.widgets import _hotswap
-
-
-EXAMPLE_NAME_SET = all_example_names()
-EXAMPLE_NAME_LIST = tuple(sorted(EXAMPLE_NAME_SET))
-
-
-def on_file_change(path, callback):
-    did_call_back_once = Event()
-
-    def watch_for_change():
-        last_modified = 0
-        while True:
-            modified_at = getmtime(path)
-            if modified_at != last_modified:
-                callback()
-                did_call_back_once.set()
-                last_modified = modified_at
-            time.sleep(1)
-
-    Thread(target=watch_for_change, daemon=True).start()
-    did_call_back_once.wait()
-
-
-def main():
-    ex_name = _example_name_input()
-
-    mount, component = _hotswap()
-
-    def update_component():
-        print(f"Loading example: {ex_name!r}")
-        mount(load_one_example(ex_name))
-
-    for file in get_example_files_by_name(ex_name):
-        on_file_change(file, update_component)
-
-    reactpy.run(component)
-
-
-def _example_name_input() -> str:
-    if len(sys.argv) == 1:
-        _print_error(
-            "No example argument given. Provide an example's number from above."
-        )
-        sys.exit(1)
-
-    ex_num = sys.argv[1]
-
-    try:
-        ex_num = int(ex_num)
-    except ValueError:
-        _print_error(
-            f"No example {ex_num!r} exists. Provide an example's number as an integer."
-        )
-        sys.exit(1)
-
-    ex_index = ex_num - 1
-    try:
-        return EXAMPLE_NAME_LIST[ex_index]
-    except IndexError:
-        _print_error(f"No example #{ex_num} exists. Choose from an option above.")
-        sys.exit(1)
-
-
-def _print_error(*args) -> None:
-    _print_available_options()
-    print(*args)
-
-
-def _print_available_options():
-    examples_by_path = {}
-    for i, name in enumerate(EXAMPLE_NAME_LIST):
-        if "/" not in name:
-            path = ""
-        else:
-            path, name = name.rsplit("/", 1)
-        examples_by_path.setdefault(path, []).append(name)
-
-    number = 1
-    print()
-    for path, names in examples_by_path.items():
-        title = " > ".join(
-            section.replace("-", " ").replace("_", " ").title()
-            for section in path.split("/")
-            if not section.startswith("_")
-        )
-        print(title)
-        print("-" * len(title))
-        for name in names:
-            print(f"{number}. ", name)
-            number += 1
-        print()
-
-
-if __name__ == "__main__":
-    main()
diff --git a/scripts/run_docs.py b/scripts/run_docs.py
deleted file mode 100644
index 31c70a038..000000000
--- a/scripts/run_docs.py
+++ /dev/null
@@ -1,20 +0,0 @@
-import os
-import sys
-
-
-# all scripts should be run from the repository root so we need to insert cwd to path
-# to import docs
-sys.path.insert(0, os.getcwd())
-
-from docs.app import make_app
-
-
-app = make_app()
-
-if __name__ == "__main__":
-    app.run(
-        host="0.0.0.0",
-        port=int(os.environ.get("PORT", 5000)),
-        workers=int(os.environ.get("WEB_CONCURRENCY", 1)),
-        debug=bool(int(os.environ.get("DEBUG", "0"))),
-    )
diff --git a/setup.py b/setup.py
deleted file mode 100644
index 5f256008f..000000000
--- a/setup.py
+++ /dev/null
@@ -1,203 +0,0 @@
-from __future__ import print_function
-
-import pipes
-import shutil
-import subprocess
-import sys
-import traceback
-from logging import StreamHandler, getLogger
-from pathlib import Path
-
-from setuptools import find_packages, setup
-from setuptools.command.develop import develop
-from setuptools.command.sdist import sdist
-
-
-if sys.platform == "win32":
-    from subprocess import list2cmdline
-else:
-
-    def list2cmdline(cmd_list):
-        return " ".join(map(pipes.quote, cmd_list))
-
-
-log = getLogger()
-log.addHandler(StreamHandler(sys.stdout))
-
-
-# --------------------------------------------------------------------------------------
-# Basic Constants
-# --------------------------------------------------------------------------------------
-
-
-# the name of the project
-NAME = "reactpy"
-
-# basic paths used to gather files
-ROOT_DIR = Path(__file__).parent
-SRC_DIR = ROOT_DIR / "src"
-PKG_DIR = SRC_DIR / NAME
-JS_DIR = SRC_DIR / "client"
-
-
-# --------------------------------------------------------------------------------------
-# Package Definition
-# --------------------------------------------------------------------------------------
-
-
-package = {
-    "name": NAME,
-    "python_requires": ">=3.7",
-    "packages": find_packages(str(SRC_DIR)),
-    "package_dir": {"": "src"},
-    "description": "It's React, but in Python",
-    "author": "Ryan Morshead",
-    "author_email": "ryan.morshead@gmail.com",
-    "url": "https://github.com/reactive-python/reactpy",
-    "license": "MIT",
-    "platforms": "Linux, Mac OS X, Windows",
-    "keywords": ["interactive", "widgets", "DOM", "React"],
-    "include_package_data": True,
-    "zip_safe": False,
-    "classifiers": [
-        "Environment :: Web Environment",
-        "Framework :: AsyncIO",
-        "Framework :: Flask",
-        "Intended Audience :: Developers",
-        "Intended Audience :: Science/Research",
-        "Programming Language :: Python :: 3.7",
-        "Programming Language :: Python :: 3.8",
-        "Programming Language :: Python :: 3.9",
-        "Topic :: Software Development :: User Interfaces",
-        "Topic :: Software Development :: Widget Sets",
-        "Typing :: Typed",
-    ],
-}
-
-
-# --------------------------------------------------------------------------------------
-# Library Version
-# --------------------------------------------------------------------------------------
-
-pkg_root_init_file = PKG_DIR / "__init__.py"
-for line in pkg_root_init_file.read_text().split("\n"):
-    if line.startswith('__version__ = "') and line.endswith('"  # DO NOT MODIFY'):
-        package["version"] = (
-            line
-            # get assignment value
-            .split("=", 1)[1]
-            # remove "DO NOT MODIFY" comment
-            .split("#", 1)[0]
-            # clean up leading/trailing space
-            .strip()
-            # remove the quotes
-            [1:-1]
-        )
-        break
-else:
-    print(f"No version found in {pkg_root_init_file}")
-    sys.exit(1)
-
-
-# --------------------------------------------------------------------------------------
-# Requirements
-# --------------------------------------------------------------------------------------
-
-
-requirements = []
-with (ROOT_DIR / "requirements" / "pkg-deps.txt").open() as f:
-    for line in map(str.strip, f):
-        if not line.startswith("#"):
-            requirements.append(line)
-package["install_requires"] = requirements
-
-_current_extras = []
-extra_requirements = {"all": []}  # type: ignore
-extra_requirements_path = ROOT_DIR / "requirements" / "pkg-extras.txt"
-with extra_requirements_path.open() as f:
-    for line in map(str.strip, f):
-        if line.startswith("#") and line[1:].strip().startswith("extra="):
-            _current_extras = [e.strip() for e in line.split("=", 1)[1].split(",")]
-            if "all" in _current_extras:
-                raise ValueError("%r uses the reserved extra name 'all'")
-            for e in _current_extras:
-                extra_requirements[e] = []
-        elif _current_extras:
-            for e in _current_extras:
-                extra_requirements[e].append(line)
-            extra_requirements["all"].append(line)
-        elif line:
-            raise ValueError(
-                f"No '# extra=<name>' header before requirements in {extra_requirements_path}"
-            )
-package["extras_require"] = extra_requirements
-
-
-# --------------------------------------------------------------------------------------
-# Library Description
-# --------------------------------------------------------------------------------------
-
-
-with (ROOT_DIR / "README.md").open() as f:
-    long_description = f.read()
-
-package["long_description"] = long_description
-package["long_description_content_type"] = "text/markdown"
-
-
-# --------------------------------------------------------------------------------------
-# Command Line Interface
-# --------------------------------------------------------------------------------------
-
-package["entry_points"] = {"console_scripts": ["reactpy=reactpy.__main__:app"]}
-
-# --------------------------------------------------------------------------------------
-# Build Javascript
-# --------------------------------------------------------------------------------------
-
-
-def build_javascript_first(cls):
-    class Command(cls):
-        def run(self):
-            log.info("Installing Javascript...")
-            try:
-                npm = shutil.which("npm")  # this is required on windows
-                if npm is None:
-                    raise RuntimeError("NPM is not installed.")
-                for args in (f"{npm} ci", f"{npm} run build"):
-                    args_list = args.split()
-                    log.info(f"> {list2cmdline(args_list)}")
-                    subprocess.run(args_list, cwd=str(JS_DIR), check=True)
-            except Exception:
-                log.error("Failed to install Javascript")
-                log.error(traceback.format_exc())
-                raise
-            else:
-                log.info("Successfully installed Javascript")
-            super().run()
-
-    return Command
-
-
-package["cmdclass"] = {
-    "sdist": build_javascript_first(sdist),
-    "develop": build_javascript_first(develop),
-}
-
-if sys.version_info < (3, 10, 6):
-    from distutils.command.build import build
-
-    package["cmdclass"]["build"] = build_javascript_first(build)
-else:
-    from setuptools.command.build_py import build_py
-
-    package["cmdclass"]["build_py"] = build_javascript_first(build_py)
-
-
-# --------------------------------------------------------------------------------------
-# Install It
-# --------------------------------------------------------------------------------------
-
-
-if __name__ == "__main__":
-    setup(**package)
diff --git a/src/client/.eslintrc.json b/src/js/.eslintrc.json
similarity index 100%
rename from src/client/.eslintrc.json
rename to src/js/.eslintrc.json
diff --git a/src/client/.gitignore b/src/js/.gitignore
similarity index 91%
rename from src/client/.gitignore
rename to src/js/.gitignore
index 52128d3e1..fedd7ea26 100644
--- a/src/client/.gitignore
+++ b/src/js/.gitignore
@@ -1,2 +1,3 @@
 tsconfig.tsbuildinfo
 packages/**/package-lock.json
+dist
diff --git a/src/client/README.md b/src/js/README.md
similarity index 100%
rename from src/client/README.md
rename to src/js/README.md
diff --git a/src/client/ui/.eslintrc.json b/src/js/app/.eslintrc.json
similarity index 100%
rename from src/client/ui/.eslintrc.json
rename to src/js/app/.eslintrc.json
diff --git a/src/client/ui/index.html b/src/js/app/index.html
similarity index 100%
rename from src/client/ui/index.html
rename to src/js/app/index.html
diff --git a/src/client/ui/package-lock.json b/src/js/app/package-lock.json
similarity index 100%
rename from src/client/ui/package-lock.json
rename to src/js/app/package-lock.json
diff --git a/src/client/ui/package.json b/src/js/app/package.json
similarity index 94%
rename from src/client/ui/package.json
rename to src/js/app/package.json
index 7c1e04a05..b9371dba3 100644
--- a/src/client/ui/package.json
+++ b/src/js/app/package.json
@@ -22,7 +22,6 @@
     "build": "vite build",
     "format": "prettier --write . && eslint --fix .",
     "test": "npm run check:tests",
-    "check:format": "prettier --check .",
     "check:tests": "echo 'no tests'",
     "check:types": "tsc --noEmit"
   }
diff --git a/src/client/ui/public/assets/reactpy-logo.ico b/src/js/app/public/assets/reactpy-logo.ico
similarity index 100%
rename from src/client/ui/public/assets/reactpy-logo.ico
rename to src/js/app/public/assets/reactpy-logo.ico
diff --git a/src/client/ui/src/index.ts b/src/js/app/src/index.ts
similarity index 100%
rename from src/client/ui/src/index.ts
rename to src/js/app/src/index.ts
diff --git a/src/client/ui/tsconfig.json b/src/js/app/tsconfig.json
similarity index 100%
rename from src/client/ui/tsconfig.json
rename to src/js/app/tsconfig.json
diff --git a/src/client/ui/vite.config.js b/src/js/app/vite.config.js
similarity index 75%
rename from src/client/ui/vite.config.js
rename to src/js/app/vite.config.js
index 6919d8e1c..c97fb6dac 100644
--- a/src/client/ui/vite.config.js
+++ b/src/js/app/vite.config.js
@@ -1,7 +1,7 @@
 import { defineConfig } from "vite";
 
 export default defineConfig({
-  build: { outDir: "../../reactpy/_client", emptyOutDir: true },
+  build: { emptyOutDir: true },
   resolve: {
     alias: {
       react: "preact/compat",
diff --git a/src/client/package-lock.json b/src/js/package-lock.json
similarity index 98%
rename from src/client/package-lock.json
rename to src/js/package-lock.json
index 109324f5a..2edfdd260 100644
--- a/src/client/package-lock.json
+++ b/src/js/package-lock.json
@@ -1,5 +1,5 @@
 {
-  "name": "client",
+  "name": "js",
   "lockfileVersion": 2,
   "requires": true,
   "packages": {
@@ -8,7 +8,7 @@
       "workspaces": [
         "packages/event-to-object",
         "packages/@reactpy/client",
-        "ui"
+        "app"
       ],
       "devDependencies": {
         "@typescript-eslint/eslint-plugin": "^5.58.0",
@@ -18,6 +18,45 @@
         "prettier": "^3.0.0-alpha.6"
       }
     },
+    "app": {
+      "license": "MIT",
+      "dependencies": {
+        "@reactpy/client": "^0.2.0",
+        "preact": "^10.7.0"
+      },
+      "devDependencies": {
+        "@types/react": "^17.0",
+        "@types/react-dom": "^17.0",
+        "typescript": "^4.9.5",
+        "vite": "^3.1.8"
+      }
+    },
+    "app/node_modules/@reactpy/client": {
+      "version": "0.2.1",
+      "resolved": "https://registry.npmjs.org/@reactpy/client/-/client-0.2.1.tgz",
+      "integrity": "sha512-9sgGH+pJ2BpLT+QSVe7FQLS2VQ9acHgPlO8X3qiTumGw43O0X82sm8pzya8H8dAew463SeGza/pZc0mpUBHmqA==",
+      "dependencies": {
+        "event-to-object": "^0.1.2",
+        "json-pointer": "^0.6.2"
+      },
+      "peerDependencies": {
+        "react": ">=16 <18",
+        "react-dom": ">=16 <18"
+      }
+    },
+    "app/node_modules/typescript": {
+      "version": "4.9.5",
+      "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
+      "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
+      "dev": true,
+      "bin": {
+        "tsc": "bin/tsc",
+        "tsserver": "bin/tsserver"
+      },
+      "engines": {
+        "node": ">=4.2.0"
+      }
+    },
     "apps/ui": {
       "extraneous": true,
       "license": "MIT",
@@ -514,6 +553,10 @@
         "url": "https://github.com/chalk/ansi-styles?sponsor=1"
       }
     },
+    "node_modules/app": {
+      "resolved": "app",
+      "link": true
+    },
     "node_modules/argparse": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
@@ -2386,10 +2429,16 @@
       "dev": true
     },
     "node_modules/nanoid": {
-      "version": "3.3.4",
-      "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
-      "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
+      "version": "3.3.6",
+      "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
+      "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==",
       "dev": true,
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
       "bin": {
         "nanoid": "bin/nanoid.cjs"
       },
@@ -2663,9 +2712,9 @@
       }
     },
     "node_modules/postcss": {
-      "version": "8.4.21",
-      "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz",
-      "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==",
+      "version": "8.4.24",
+      "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.24.tgz",
+      "integrity": "sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg==",
       "dev": true,
       "funding": [
         {
@@ -2675,10 +2724,14 @@
         {
           "type": "tidelift",
           "url": "https://tidelift.com/funding/github/npm/postcss"
+        },
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
         }
       ],
       "dependencies": {
-        "nanoid": "^3.3.4",
+        "nanoid": "^3.3.6",
         "picocolors": "^1.0.0",
         "source-map-js": "^1.0.2"
       },
@@ -2687,9 +2740,9 @@
       }
     },
     "node_modules/preact": {
-      "version": "10.13.1",
-      "resolved": "https://registry.npmjs.org/preact/-/preact-10.13.1.tgz",
-      "integrity": "sha512-KyoXVDU5OqTpG9LXlB3+y639JAGzl8JSBXLn1J9HTSB3gbKcuInga7bZnXLlxmK94ntTs1EFeZp0lrja2AuBYQ==",
+      "version": "10.15.1",
+      "resolved": "https://registry.npmjs.org/preact/-/preact-10.15.1.tgz",
+      "integrity": "sha512-qs2ansoQEwzNiV5eAcRT1p1EC/dmEzaATVDJNiB3g2sRDWdA7b7MurXdJjB2+/WQktGWZwxvDrnuRFbWuIr64g==",
       "funding": {
         "type": "opencollective",
         "url": "https://opencollective.com/preact"
@@ -2810,12 +2863,12 @@
       }
     },
     "node_modules/resolve": {
-      "version": "1.22.1",
-      "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
-      "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==",
+      "version": "1.22.2",
+      "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz",
+      "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==",
       "dev": true,
       "dependencies": {
-        "is-core-module": "^2.9.0",
+        "is-core-module": "^2.11.0",
         "path-parse": "^1.0.7",
         "supports-preserve-symlinks-flag": "^1.0.0"
       },
@@ -3232,10 +3285,6 @@
         "node": ">=12.20"
       }
     },
-    "node_modules/ui": {
-      "resolved": "ui",
-      "link": true
-    },
     "node_modules/unbox-primitive": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz",
@@ -3279,9 +3328,9 @@
       }
     },
     "node_modules/vite": {
-      "version": "3.2.5",
-      "resolved": "https://registry.npmjs.org/vite/-/vite-3.2.5.tgz",
-      "integrity": "sha512-4mVEpXpSOgrssFZAOmGIr85wPHKvaDAcXqxVxVRZhljkJOMZi1ibLibzjLHzJvcok8BMguLc7g1W6W/GqZbLdQ==",
+      "version": "3.2.7",
+      "resolved": "https://registry.npmjs.org/vite/-/vite-3.2.7.tgz",
+      "integrity": "sha512-29pdXjk49xAP0QBr0xXqu2s5jiQIXNvE/xwd0vUizYT2Hzqe4BksNNoWllFVXJf4eLZ+UlVQmXfB4lWrc+t18g==",
       "dev": true,
       "dependencies": {
         "esbuild": "^0.15.9",
@@ -3458,7 +3507,7 @@
       }
     },
     "packages/@reactpy/client": {
-      "version": "0.3.0",
+      "version": "0.3.1",
       "license": "MIT",
       "dependencies": {
         "event-to-object": "^0.1.2",
@@ -3556,6 +3605,7 @@
       "extraneous": true
     },
     "ui": {
+      "extraneous": true,
       "license": "MIT",
       "dependencies": {
         "@reactpy/client": "^0.2.0",
@@ -3567,32 +3617,6 @@
         "typescript": "^4.9.5",
         "vite": "^3.1.8"
       }
-    },
-    "ui/node_modules/@reactpy/client": {
-      "version": "0.2.1",
-      "resolved": "https://registry.npmjs.org/@reactpy/client/-/client-0.2.1.tgz",
-      "integrity": "sha512-9sgGH+pJ2BpLT+QSVe7FQLS2VQ9acHgPlO8X3qiTumGw43O0X82sm8pzya8H8dAew463SeGza/pZc0mpUBHmqA==",
-      "dependencies": {
-        "event-to-object": "^0.1.2",
-        "json-pointer": "^0.6.2"
-      },
-      "peerDependencies": {
-        "react": ">=16 <18",
-        "react-dom": ">=16 <18"
-      }
-    },
-    "ui/node_modules/typescript": {
-      "version": "4.9.5",
-      "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
-      "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
-      "dev": true,
-      "bin": {
-        "tsc": "bin/tsc",
-        "tsserver": "bin/tsserver"
-      },
-      "engines": {
-        "node": ">=4.2.0"
-      }
     }
   },
   "dependencies": {
@@ -3923,6 +3947,34 @@
         "color-convert": "^2.0.1"
       }
     },
+    "app": {
+      "version": "file:app",
+      "requires": {
+        "@reactpy/client": "^0.2.0",
+        "@types/react": "^17.0",
+        "@types/react-dom": "^17.0",
+        "preact": "^10.7.0",
+        "typescript": "^4.9.5",
+        "vite": "^3.1.8"
+      },
+      "dependencies": {
+        "@reactpy/client": {
+          "version": "0.2.1",
+          "resolved": "https://registry.npmjs.org/@reactpy/client/-/client-0.2.1.tgz",
+          "integrity": "sha512-9sgGH+pJ2BpLT+QSVe7FQLS2VQ9acHgPlO8X3qiTumGw43O0X82sm8pzya8H8dAew463SeGza/pZc0mpUBHmqA==",
+          "requires": {
+            "event-to-object": "^0.1.2",
+            "json-pointer": "^0.6.2"
+          }
+        },
+        "typescript": {
+          "version": "4.9.5",
+          "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
+          "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
+          "dev": true
+        }
+      }
+    },
     "argparse": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
@@ -5233,9 +5285,9 @@
       "dev": true
     },
     "nanoid": {
-      "version": "3.3.4",
-      "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
-      "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
+      "version": "3.3.6",
+      "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
+      "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==",
       "dev": true
     },
     "natural-compare": {
@@ -5424,20 +5476,20 @@
       "dev": true
     },
     "postcss": {
-      "version": "8.4.21",
-      "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz",
-      "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==",
+      "version": "8.4.24",
+      "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.24.tgz",
+      "integrity": "sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg==",
       "dev": true,
       "requires": {
-        "nanoid": "^3.3.4",
+        "nanoid": "^3.3.6",
         "picocolors": "^1.0.0",
         "source-map-js": "^1.0.2"
       }
     },
     "preact": {
-      "version": "10.13.1",
-      "resolved": "https://registry.npmjs.org/preact/-/preact-10.13.1.tgz",
-      "integrity": "sha512-KyoXVDU5OqTpG9LXlB3+y639JAGzl8JSBXLn1J9HTSB3gbKcuInga7bZnXLlxmK94ntTs1EFeZp0lrja2AuBYQ=="
+      "version": "10.15.1",
+      "resolved": "https://registry.npmjs.org/preact/-/preact-10.15.1.tgz",
+      "integrity": "sha512-qs2ansoQEwzNiV5eAcRT1p1EC/dmEzaATVDJNiB3g2sRDWdA7b7MurXdJjB2+/WQktGWZwxvDrnuRFbWuIr64g=="
     },
     "prelude-ls": {
       "version": "1.2.1",
@@ -5513,12 +5565,12 @@
       }
     },
     "resolve": {
-      "version": "1.22.1",
-      "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
-      "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==",
+      "version": "1.22.2",
+      "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz",
+      "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==",
       "dev": true,
       "requires": {
-        "is-core-module": "^2.9.0",
+        "is-core-module": "^2.11.0",
         "path-parse": "^1.0.7",
         "supports-preserve-symlinks-flag": "^1.0.0"
       }
@@ -5802,34 +5854,6 @@
       "dev": true,
       "peer": true
     },
-    "ui": {
-      "version": "file:ui",
-      "requires": {
-        "@reactpy/client": "^0.2.0",
-        "@types/react": "^17.0",
-        "@types/react-dom": "^17.0",
-        "preact": "^10.7.0",
-        "typescript": "^4.9.5",
-        "vite": "^3.1.8"
-      },
-      "dependencies": {
-        "@reactpy/client": {
-          "version": "0.2.1",
-          "resolved": "https://registry.npmjs.org/@reactpy/client/-/client-0.2.1.tgz",
-          "integrity": "sha512-9sgGH+pJ2BpLT+QSVe7FQLS2VQ9acHgPlO8X3qiTumGw43O0X82sm8pzya8H8dAew463SeGza/pZc0mpUBHmqA==",
-          "requires": {
-            "event-to-object": "^0.1.2",
-            "json-pointer": "^0.6.2"
-          }
-        },
-        "typescript": {
-          "version": "4.9.5",
-          "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
-          "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
-          "dev": true
-        }
-      }
-    },
     "unbox-primitive": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz",
@@ -5864,9 +5888,9 @@
       }
     },
     "vite": {
-      "version": "3.2.5",
-      "resolved": "https://registry.npmjs.org/vite/-/vite-3.2.5.tgz",
-      "integrity": "sha512-4mVEpXpSOgrssFZAOmGIr85wPHKvaDAcXqxVxVRZhljkJOMZi1ibLibzjLHzJvcok8BMguLc7g1W6W/GqZbLdQ==",
+      "version": "3.2.7",
+      "resolved": "https://registry.npmjs.org/vite/-/vite-3.2.7.tgz",
+      "integrity": "sha512-29pdXjk49xAP0QBr0xXqu2s5jiQIXNvE/xwd0vUizYT2Hzqe4BksNNoWllFVXJf4eLZ+UlVQmXfB4lWrc+t18g==",
       "dev": true,
       "requires": {
         "esbuild": "^0.15.9",
diff --git a/src/client/package.json b/src/js/package.json
similarity index 62%
rename from src/client/package.json
rename to src/js/package.json
index 3992d59ed..dc6dc7fca 100644
--- a/src/client/package.json
+++ b/src/js/package.json
@@ -4,15 +4,17 @@
     "publish": "npm --workspaces publish",
     "test": "npm --workspaces test",
     "build": "npm --workspaces run build",
-    "format": "prettier --write . && eslint --fix .",
-    "check:format": "prettier --check . && eslint .",
+    "format": "npm run prettier -- --write && npm run eslint -- --fix",
+    "check:format": "npm run prettier -- --check && npm run eslint",
     "check:tests": "npm --workspaces run check:tests",
-    "check:types": "npm --workspaces run check:types"
+    "check:types": "npm --workspaces run check:types",
+    "prettier": "prettier --ignore-path .gitignore .",
+    "eslint": "eslint --ignore-path .gitignore ."
   },
   "workspaces": [
     "packages/event-to-object",
     "packages/@reactpy/client",
-    "ui"
+    "app"
   ],
   "devDependencies": {
     "@typescript-eslint/eslint-plugin": "^5.58.0",
diff --git a/src/client/packages/@reactpy/client/.gitignore b/src/js/packages/@reactpy/client/.gitignore
similarity index 100%
rename from src/client/packages/@reactpy/client/.gitignore
rename to src/js/packages/@reactpy/client/.gitignore
diff --git a/src/client/packages/@reactpy/client/README.md b/src/js/packages/@reactpy/client/README.md
similarity index 100%
rename from src/client/packages/@reactpy/client/README.md
rename to src/js/packages/@reactpy/client/README.md
diff --git a/src/client/packages/@reactpy/client/package.json b/src/js/packages/@reactpy/client/package.json
similarity index 100%
rename from src/client/packages/@reactpy/client/package.json
rename to src/js/packages/@reactpy/client/package.json
diff --git a/src/client/packages/@reactpy/client/src/components.tsx b/src/js/packages/@reactpy/client/src/components.tsx
similarity index 100%
rename from src/client/packages/@reactpy/client/src/components.tsx
rename to src/js/packages/@reactpy/client/src/components.tsx
diff --git a/src/client/packages/@reactpy/client/src/index.ts b/src/js/packages/@reactpy/client/src/index.ts
similarity index 100%
rename from src/client/packages/@reactpy/client/src/index.ts
rename to src/js/packages/@reactpy/client/src/index.ts
diff --git a/src/client/packages/@reactpy/client/src/logger.ts b/src/js/packages/@reactpy/client/src/logger.ts
similarity index 100%
rename from src/client/packages/@reactpy/client/src/logger.ts
rename to src/js/packages/@reactpy/client/src/logger.ts
diff --git a/src/client/packages/@reactpy/client/src/messages.ts b/src/js/packages/@reactpy/client/src/messages.ts
similarity index 100%
rename from src/client/packages/@reactpy/client/src/messages.ts
rename to src/js/packages/@reactpy/client/src/messages.ts
diff --git a/src/client/packages/@reactpy/client/src/mount.tsx b/src/js/packages/@reactpy/client/src/mount.tsx
similarity index 100%
rename from src/client/packages/@reactpy/client/src/mount.tsx
rename to src/js/packages/@reactpy/client/src/mount.tsx
diff --git a/src/client/packages/@reactpy/client/src/reactpy-client.ts b/src/js/packages/@reactpy/client/src/reactpy-client.ts
similarity index 100%
rename from src/client/packages/@reactpy/client/src/reactpy-client.ts
rename to src/js/packages/@reactpy/client/src/reactpy-client.ts
diff --git a/src/client/packages/@reactpy/client/src/reactpy-vdom.tsx b/src/js/packages/@reactpy/client/src/reactpy-vdom.tsx
similarity index 100%
rename from src/client/packages/@reactpy/client/src/reactpy-vdom.tsx
rename to src/js/packages/@reactpy/client/src/reactpy-vdom.tsx
diff --git a/src/client/packages/@reactpy/client/tsconfig.json b/src/js/packages/@reactpy/client/tsconfig.json
similarity index 100%
rename from src/client/packages/@reactpy/client/tsconfig.json
rename to src/js/packages/@reactpy/client/tsconfig.json
diff --git a/src/client/packages/event-to-object/package.json b/src/js/packages/event-to-object/package.json
similarity index 100%
rename from src/client/packages/event-to-object/package.json
rename to src/js/packages/event-to-object/package.json
diff --git a/src/client/packages/event-to-object/src/events.ts b/src/js/packages/event-to-object/src/events.ts
similarity index 100%
rename from src/client/packages/event-to-object/src/events.ts
rename to src/js/packages/event-to-object/src/events.ts
diff --git a/src/client/packages/event-to-object/src/index.ts b/src/js/packages/event-to-object/src/index.ts
similarity index 100%
rename from src/client/packages/event-to-object/src/index.ts
rename to src/js/packages/event-to-object/src/index.ts
diff --git a/src/client/packages/event-to-object/tests/event-to-object.test.ts b/src/js/packages/event-to-object/tests/event-to-object.test.ts
similarity index 100%
rename from src/client/packages/event-to-object/tests/event-to-object.test.ts
rename to src/js/packages/event-to-object/tests/event-to-object.test.ts
diff --git a/src/client/packages/event-to-object/tests/tooling/check.ts b/src/js/packages/event-to-object/tests/tooling/check.ts
similarity index 100%
rename from src/client/packages/event-to-object/tests/tooling/check.ts
rename to src/js/packages/event-to-object/tests/tooling/check.ts
diff --git a/src/client/packages/event-to-object/tests/tooling/mock.ts b/src/js/packages/event-to-object/tests/tooling/mock.ts
similarity index 100%
rename from src/client/packages/event-to-object/tests/tooling/mock.ts
rename to src/js/packages/event-to-object/tests/tooling/mock.ts
diff --git a/src/client/packages/event-to-object/tests/tooling/setup.js b/src/js/packages/event-to-object/tests/tooling/setup.js
similarity index 100%
rename from src/client/packages/event-to-object/tests/tooling/setup.js
rename to src/js/packages/event-to-object/tests/tooling/setup.js
diff --git a/src/client/packages/event-to-object/tsconfig.json b/src/js/packages/event-to-object/tsconfig.json
similarity index 100%
rename from src/client/packages/event-to-object/tsconfig.json
rename to src/js/packages/event-to-object/tsconfig.json
diff --git a/src/client/packages/event-to-object/tsconfig.tests.json b/src/js/packages/event-to-object/tsconfig.tests.json
similarity index 100%
rename from src/client/packages/event-to-object/tsconfig.tests.json
rename to src/js/packages/event-to-object/tsconfig.tests.json
diff --git a/src/client/tsconfig.package.json b/src/js/tsconfig.package.json
similarity index 100%
rename from src/client/tsconfig.package.json
rename to src/js/tsconfig.package.json
diff --git a/src/py/reactpy/.gitignore b/src/py/reactpy/.gitignore
new file mode 100644
index 000000000..0499d7590
--- /dev/null
+++ b/src/py/reactpy/.gitignore
@@ -0,0 +1,4 @@
+.coverage.*
+
+# --- Build Artifacts ---
+reactpy/_static
diff --git a/MANIFEST.in b/src/py/reactpy/MANIFEST.in
similarity index 100%
rename from MANIFEST.in
rename to src/py/reactpy/MANIFEST.in
diff --git a/src/py/reactpy/README.md b/src/py/reactpy/README.md
new file mode 100644
index 000000000..910a573a5
--- /dev/null
+++ b/src/py/reactpy/README.md
@@ -0,0 +1,23 @@
+# <img src="https://raw.githubusercontent.com/reactive-python/reactpy/main/branding/svg/reactpy-logo-square.svg" align="left" height="45"/> ReactPy
+
+<p>
+    <a href="https://github.com/reactive-python/reactpy/actions">
+        <img src="https://github.com/reactive-python/reactpy/workflows/test/badge.svg?event=push">
+    </a>
+    <a href="https://pypi.org/project/reactpy/">
+        <img src="https://img.shields.io/pypi/v/reactpy.svg?label=PyPI">
+    </a>
+    <a href="https://github.com/reactive-python/reactpy/blob/main/LICENSE">
+        <img src="https://img.shields.io/badge/License-MIT-purple.svg">
+    </a>
+    <a href="https://reactpy.dev/">
+        <img src="https://img.shields.io/website?down_message=offline&label=Docs&logo=read-the-docs&logoColor=white&up_message=online&url=https%3A%2F%2Freactpy.dev%2Fdocs%2Findex.html">
+    </a>
+    <a href="https://discord.gg/uNb5P4hA9X">
+        <img src="https://img.shields.io/discord/1111078259854168116?label=Discord&logo=discord">
+    </a>
+</p>
+
+---
+
+[ReactPy](https://reactpy.dev/) is a library for building user interfaces in Python without Javascript. ReactPy interfaces are made from components that look and behave similar to those found in [ReactJS](https://reactjs.org/). Designed with simplicity in mind, ReactPy can be used by those without web development experience while also being powerful enough to grow with your ambitions.
diff --git a/src/py/reactpy/pyproject.toml b/src/py/reactpy/pyproject.toml
new file mode 100644
index 000000000..659ddbf94
--- /dev/null
+++ b/src/py/reactpy/pyproject.toml
@@ -0,0 +1,175 @@
+[build-system]
+requires = ["hatchling", "hatch-build-scripts>=0.0.4"]
+build-backend = "hatchling.build"
+
+# --- Project --------------------------------------------------------------------------
+
+[project]
+name = "reactpy"
+dynamic = ["version"]
+description = 'Reactive user interfaces with pure Python'
+readme = "README.md"
+requires-python = ">=3.9"
+license = "MIT"
+keywords = ["react", "javascript", "reactpy", "component"]
+authors = [
+  { name = "Ryan Morshead", email = "ryan.morshead@gmail.com" },
+]
+classifiers = [
+  "Development Status :: 4 - Beta",
+  "Programming Language :: Python",
+  "Programming Language :: Python :: 3.9",
+  "Programming Language :: Python :: 3.10",
+  "Programming Language :: Python :: 3.11",
+  "Programming Language :: Python :: Implementation :: CPython",
+  "Programming Language :: Python :: Implementation :: PyPy",
+]
+dependencies = [
+  "typing-extensions >=3.10",
+  "mypy-extensions >=0.4.3",
+  "anyio >=3",
+  "jsonpatch >=1.32",
+  "fastjsonschema >=2.14.5",
+  "requests >=2",
+  "colorlog >=6",
+  "asgiref >=3",
+  "lxml >=4",
+]
+[project.optional-dependencies]
+all = ["reactpy[starlette,sanic,fastapi,flask,tornado,testing]"]
+
+starlette = [
+  "starlette >=0.13.6",
+  "uvicorn[standard] >=0.19.0",
+]
+sanic = [
+  "sanic >=21",
+  "sanic-cors",
+  "uvicorn[standard] >=0.19.0",
+]
+fastapi = [
+  "fastapi >=0.63.0",
+  "uvicorn[standard] >=0.19.0",
+]
+flask = [
+  "flask",
+  "markupsafe>=1.1.1,<2.1",
+  "flask-cors",
+  "flask-sock",
+]
+tornado = [
+  "tornado",
+]
+testing = [
+  "playwright",
+]
+
+[project.urls]
+Source = "https://github.com/reactive-python/reactpy"
+Documentation = "https://github.com/reactive-python/reactpy#readme"
+Issues = "https://github.com/reactive-python/reactpy/discussions"
+
+# --- Hatch ----------------------------------------------------------------------------
+
+[tool.hatch.version]
+path = "reactpy/__init__.py"
+
+[tool.hatch.envs.default]
+features = ["all"]
+pre-install-command = "hatch build --hooks-only"
+dependencies = [
+  "coverage[toml]>=6.5",
+  "pytest",
+  "pytest-asyncio>=0.17",
+  "pytest-mock",
+  "pytest-rerunfailures",
+  "pytest-timeout",
+  "responses",
+  "playwright",
+  # I'm not quite sure why this needs to be installed for tests with Sanic to pass
+  "sanic-testing",
+  # Used to generate model changes from layout update messages
+  "jsonpointer",
+]
+[tool.hatch.envs.default.scripts]
+test = "playwright install && pytest {args:tests}"
+test-cov = "playwright install && coverage run -m pytest {args:tests}"
+cov-report = [
+  # "- coverage combine",
+  "coverage report",
+]
+cov = [
+  "test-cov {args}",
+  "cov-report",
+]
+
+[tool.hatch.envs.default.env-vars]
+REACTPY_DEBUG_MODE="1"
+
+[tool.hatch.envs.lint]
+features = ["all"]
+dependencies = [
+  "mypy>=1.0.0",
+  "types-click",
+  "types-tornado",
+  "types-pkg-resources",
+  "types-flask",
+  "types-requests",
+]
+
+[tool.hatch.envs.lint.scripts]
+types = "mypy --strict reactpy"
+all = ["types"]
+
+[[tool.hatch.build.hooks.build-scripts.scripts]]
+work_dir = "../../js"
+out_dir = "reactpy/_static"
+commands = [
+  "npm ci",
+  "npm run build"
+]
+artifacts = [
+  "app/dist/"
+]
+
+# --- Pytest ---------------------------------------------------------------------------
+
+[tool.pytest.ini_options]
+testpaths = "tests"
+xfail_strict = true
+python_files = "*asserts.py test_*.py"
+asyncio_mode = "auto"
+
+# --- MyPy -----------------------------------------------------------------------------
+
+[tool.mypy]
+incremental = false
+ignore_missing_imports = true
+warn_unused_configs = true
+warn_redundant_casts = true
+warn_unused_ignores = true
+
+# --- Coverage -------------------------------------------------------------------------
+
+[tool.coverage.run]
+source_pkgs = ["reactpy"]
+branch = false
+parallel = false
+omit = [
+  "reactpy/__init__.py",
+]
+
+[tool.coverage.report]
+fail_under = 100
+show_missing = true
+skip_covered = true
+sort = "Name"
+exclude_lines = [
+  "no ?cov",
+  '\.\.\.',
+  "if __name__ == .__main__.:",
+  "if TYPE_CHECKING:",
+]
+omit = [
+    "reactpy/__main__.py",
+]
diff --git a/src/reactpy/__init__.py b/src/py/reactpy/reactpy/__init__.py
similarity index 99%
rename from src/reactpy/__init__.py
rename to src/py/reactpy/reactpy/__init__.py
index c9adf6cb2..996a984b2 100644
--- a/src/reactpy/__init__.py
+++ b/src/py/reactpy/reactpy/__init__.py
@@ -20,7 +20,6 @@
 from reactpy.core.vdom import vdom
 from reactpy.utils import Ref, html_to_vdom, vdom_to_html
 
-
 __author__ = "The Reactive Python Team"
 __version__ = "1.0.0"  # DO NOT MODIFY
 
diff --git a/src/reactpy/__main__.py b/src/py/reactpy/reactpy/__main__.py
similarity index 100%
rename from src/reactpy/__main__.py
rename to src/py/reactpy/reactpy/__main__.py
diff --git a/src/reactpy/_console/__init__.py b/src/py/reactpy/reactpy/_console/__init__.py
similarity index 100%
rename from src/reactpy/_console/__init__.py
rename to src/py/reactpy/reactpy/_console/__init__.py
diff --git a/src/reactpy/_console/ast_utils.py b/src/py/reactpy/reactpy/_console/ast_utils.py
similarity index 93%
rename from src/reactpy/_console/ast_utils.py
rename to src/py/reactpy/reactpy/_console/ast_utils.py
index aeaf8f512..180cb0e4b 100644
--- a/src/reactpy/_console/ast_utils.py
+++ b/src/py/reactpy/reactpy/_console/ast_utils.py
@@ -1,13 +1,13 @@
 from __future__ import annotations
 
 import ast
-from collections.abc import Sequence
+from collections.abc import Iterator, Sequence
 from dataclasses import dataclass
 from pathlib import Path
 from textwrap import indent
 from tokenize import COMMENT as COMMENT_TOKEN
 from tokenize import generate_tokens
-from typing import Any, Iterator
+from typing import Any
 
 import click
 
@@ -37,12 +37,13 @@ def rewrite_changed_nodes(
             ):
                 nodes_to_unparse.append(current_node)
                 break
-        else:  # pragma: no cover
-            raise RuntimeError("Failed to change code")
+        else:  # nocov
+            msg = "Failed to change code"
+            raise RuntimeError(msg)
 
     # check if an nodes to rewrite contain eachother, pick outermost nodes
-    current_outermost_node, *sorted_nodes_to_unparse = list(
-        sorted(nodes_to_unparse, key=lambda n: n.lineno)
+    current_outermost_node, *sorted_nodes_to_unparse = sorted(
+        nodes_to_unparse, key=lambda n: n.lineno
     )
     outermost_nodes_to_unparse = [current_outermost_node]
     for node in sorted_nodes_to_unparse:
@@ -80,7 +81,7 @@ def rewrite_changed_nodes(
         if comments:
             moved_comment_lines_from_end.append(len(lines) - node.lineno)
 
-    for lineno_from_end in sorted(list(set(moved_comment_lines_from_end))):
+    for lineno_from_end in sorted(set(moved_comment_lines_from_end)):
         click.echo(f"Moved comments to {file}:{len(lines) - lineno_from_end}")
 
     return "\n".join(lines)
@@ -128,7 +129,7 @@ def find_element_constructor_usages(
                     node.args.insert(1, maybe_attr_dict_node)
                 else:
                     continue
-            elif len(node.args) >= 2:
+            elif len(node.args) >= 2:  # noqa: PLR2004
                 maybe_attr_dict_node = node.args[1]
         elif hasattr(html, name):
             if len(node.args) == 0:
@@ -179,7 +180,7 @@ def _find_comments(lines: list[str]) -> list[str]:
 def _walk_with_parent(
     node: ast.AST, parents: tuple[ast.AST, ...] = ()
 ) -> Iterator[tuple[tuple[ast.AST, ...], ast.AST]]:
-    parents = (node,) + parents
+    parents = (node, *parents)
     for child in ast.iter_child_nodes(node):
         yield parents, child
         yield from _walk_with_parent(child, parents)
diff --git a/src/reactpy/_console/rewrite_camel_case_props.py b/src/py/reactpy/reactpy/_console/rewrite_camel_case_props.py
similarity index 96%
rename from src/reactpy/_console/rewrite_camel_case_props.py
rename to src/py/reactpy/reactpy/_console/rewrite_camel_case_props.py
index b67c210ab..e5d1860c2 100644
--- a/src/reactpy/_console/rewrite_camel_case_props.py
+++ b/src/py/reactpy/reactpy/_console/rewrite_camel_case_props.py
@@ -16,7 +16,6 @@
     rewrite_changed_nodes,
 )
 
-
 CAMEL_CASE_SUB_PATTERN = re.compile(r"(?<!^)(?=[A-Z])")
 
 
@@ -24,8 +23,9 @@
 @click.argument("paths", nargs=-1, type=click.Path(exists=True))
 def rewrite_camel_case_props(paths: list[str]) -> None:
     """Rewrite camelCase props to snake_case"""
-    if sys.version_info < (3, 9):  # pragma: no cover
-        raise RuntimeError("This command requires Python>=3.9")
+    if sys.version_info < (3, 9):  # nocov
+        msg = "This command requires Python>=3.9"
+        raise RuntimeError(msg)
 
     for p in map(Path, paths):
         for f in [p] if p.is_file() else p.rglob("*.py"):
diff --git a/src/reactpy/_console/rewrite_keys.py b/src/py/reactpy/reactpy/_console/rewrite_keys.py
similarity index 96%
rename from src/reactpy/_console/rewrite_keys.py
rename to src/py/reactpy/reactpy/_console/rewrite_keys.py
index 7ff672e1e..cbd90351a 100644
--- a/src/reactpy/_console/rewrite_keys.py
+++ b/src/py/reactpy/reactpy/_console/rewrite_keys.py
@@ -45,8 +45,9 @@ def rewrite_keys(paths: list[str]) -> None:
     just above its changes. As such it requires manual intervention to put those
     comments back in their original location.
     """
-    if sys.version_info < (3, 9):  # pragma: no cover
-        raise RuntimeError("This command requires Python>=3.9")
+    if sys.version_info < (3, 9):  # nocov
+        msg = "This command requires Python>=3.9"
+        raise RuntimeError(msg)
 
     for p in map(Path, paths):
         for f in [p] if p.is_file() else p.rglob("*.py"):
diff --git a/src/reactpy/_option.py b/src/py/reactpy/reactpy/_option.py
similarity index 91%
rename from src/reactpy/_option.py
rename to src/py/reactpy/reactpy/_option.py
index b86b7c0cc..1421f33a3 100644
--- a/src/reactpy/_option.py
+++ b/src/py/reactpy/reactpy/_option.py
@@ -6,7 +6,6 @@
 
 from reactpy._warnings import warn
 
-
 _O = TypeVar("_O")
 logger = getLogger(__name__)
 
@@ -63,12 +62,12 @@ def current(self) -> _O:
     @current.setter
     def current(self, new: _O) -> None:
         self.set_current(new)
-        return None
 
     def subscribe(self, handler: Callable[[_O], None]) -> Callable[[_O], None]:
         """Register a callback that will be triggered when this option changes"""
         if not self.mutable:
-            raise TypeError("Immutable options cannot be subscribed to.")
+            msg = "Immutable options cannot be subscribed to."
+            raise TypeError(msg)
         self._subscribers.append(handler)
         handler(self.current)
         return handler
@@ -83,7 +82,8 @@ def set_current(self, new: Any) -> None:
         Raises a ``TypeError`` if this option is not :attr:`Option.mutable`.
         """
         if not self._mutable:
-            raise TypeError(f"{self} cannot be modified after initial load")
+            msg = f"{self} cannot be modified after initial load"
+            raise TypeError(msg)
         old = self.current
         new = self._current = self._validator(new)
         logger.debug(f"{self._name}={self._current}")
@@ -107,7 +107,8 @@ def reload(self) -> None:
     def unset(self) -> None:
         """Remove the current value, the default will be used until it is set again."""
         if not self._mutable:
-            raise TypeError(f"{self} cannot be modified after initial load")
+            msg = f"{self} cannot be modified after initial load"
+            raise TypeError(msg)
         old = self.current
         delattr(self, "_current")
         if self.current != old:
@@ -118,7 +119,7 @@ def __repr__(self) -> str:
         return f"Option({self._name}={self.current!r})"
 
 
-class DeprecatedOption(Option[_O]):  # pragma: no cover
+class DeprecatedOption(Option[_O]):  # nocov
     def __init__(self, message: str, *args: Any, **kwargs: Any) -> None:
         self._deprecation_message = message
         super().__init__(*args, **kwargs)
diff --git a/src/reactpy/_warnings.py b/src/py/reactpy/reactpy/_warnings.py
similarity index 88%
rename from src/reactpy/_warnings.py
rename to src/py/reactpy/reactpy/_warnings.py
index 573a5ab70..678b5f868 100644
--- a/src/reactpy/_warnings.py
+++ b/src/py/reactpy/reactpy/_warnings.py
@@ -1,7 +1,8 @@
+from collections.abc import Iterator
 from functools import wraps
 from inspect import currentframe
 from types import FrameType
-from typing import TYPE_CHECKING, Any, Iterator
+from typing import TYPE_CHECKING, Any
 from warnings import warn as _warn
 
 
@@ -12,7 +13,7 @@ def warn(*args: Any, **kwargs: Any) -> Any:
 
 
 if TYPE_CHECKING:
-    warn = _warn
+    warn = _warn  # noqa F811
 
 
 def _frame_depth_in_module() -> int:
diff --git a/src/reactpy/backend/__init__.py b/src/py/reactpy/reactpy/backend/__init__.py
similarity index 100%
rename from src/reactpy/backend/__init__.py
rename to src/py/reactpy/reactpy/backend/__init__.py
diff --git a/src/reactpy/backend/_common.py b/src/py/reactpy/reactpy/backend/_common.py
similarity index 91%
rename from src/reactpy/backend/_common.py
rename to src/py/reactpy/reactpy/backend/_common.py
index 41bff8512..c2e9ee583 100644
--- a/src/reactpy/backend/_common.py
+++ b/src/py/reactpy/reactpy/backend/_common.py
@@ -2,9 +2,10 @@
 
 import asyncio
 import os
+from collections.abc import Awaitable, Sequence
 from dataclasses import dataclass
 from pathlib import Path, PurePosixPath
-from typing import Any, Awaitable, Sequence, cast
+from typing import Any, cast
 
 import uvicorn
 from asgiref.typing import ASGIApplication
@@ -15,13 +16,12 @@
 from reactpy.core.types import VdomDict
 from reactpy.utils import vdom_to_html
 
-
 PATH_PREFIX = PurePosixPath("/_reactpy")
 MODULES_PATH = PATH_PREFIX / "modules"
 ASSETS_PATH = PATH_PREFIX / "assets"
 STREAM_PATH = PATH_PREFIX / "stream"
 
-CLIENT_BUILD_DIR = Path(_reactpy_file_path).parent / "_client"
+CLIENT_BUILD_DIR = Path(_reactpy_file_path).parent / "_static" / "app" / "dist"
 
 
 async def serve_development_asgi(
@@ -53,7 +53,7 @@ async def serve_development_asgi(
         # Since we aren't using the uvicorn's `run()` API, we can't guarantee uvicorn's
         # order of operations. So we need to make sure `shutdown()` always has an initialized
         # list of `self.servers` to use.
-        if not hasattr(server, "servers"):  # pragma: no cover
+        if not hasattr(server, "servers"):  # nocov
             server.servers = []
         await asyncio.wait_for(server.shutdown(), timeout=3)
 
@@ -87,7 +87,8 @@ def traversal_safe_path(root: str | Path, *unsafe: str | Path) -> Path:
 
     if os.path.commonprefix([root, path]) != root:
         # If the common prefix is not root directory we resolved outside the root dir
-        raise ValueError("Unsafe path")
+        msg = "Unsafe path"
+        raise ValueError(msg)
 
     return Path(path)
 
@@ -136,4 +137,5 @@ class CommonOptions:
 
     def __post_init__(self) -> None:
         if self.url_prefix and not self.url_prefix.startswith("/"):
-            raise ValueError("Expected 'url_prefix' to start with '/'")
+            msg = "Expected 'url_prefix' to start with '/'"
+            raise ValueError(msg)
diff --git a/src/reactpy/backend/default.py b/src/py/reactpy/reactpy/backend/default.py
similarity index 71%
rename from src/reactpy/backend/default.py
rename to src/py/reactpy/reactpy/backend/default.py
index b24bfc314..eb27ca93b 100644
--- a/src/reactpy/backend/default.py
+++ b/src/py/reactpy/reactpy/backend/default.py
@@ -5,12 +5,10 @@
 from sys import exc_info
 from typing import Any, NoReturn
 
+from reactpy.backend.types import BackendImplementation
+from reactpy.backend.utils import all_implementations
 from reactpy.types import RootComponentConstructor
 
-from .types import BackendImplementation
-from .utils import all_implementations
-
-
 logger = getLogger(__name__)
 
 
@@ -18,8 +16,9 @@ def configure(
     app: Any, component: RootComponentConstructor, options: None = None
 ) -> None:
     """Configure the given app instance to display the given component"""
-    if options is not None:  # pragma: no cover
-        raise ValueError("Default implementation cannot be configured with options")
+    if options is not None:  # nocov
+        msg = "Default implementation cannot be configured with options"
+        raise ValueError(msg)
     return _default_implementation().configure(app, component)
 
 
@@ -28,9 +27,10 @@ def create_development_app() -> Any:
     return _default_implementation().create_development_app()
 
 
-def Options(*args: Any, **kwargs: Any) -> NoReturn:
+def Options(*args: Any, **kwargs: Any) -> NoReturn:  # nocov
     """Create configuration options"""
-    raise ValueError("Default implementation has no options.")  # pragma: no cover
+    msg = "Default implementation has no options."
+    raise ValueError(msg)
 
 
 async def serve_development_app(
@@ -50,16 +50,17 @@ async def serve_development_app(
 
 def _default_implementation() -> BackendImplementation[Any]:
     """Get the first available server implementation"""
-    global _DEFAULT_IMPLEMENTATION
+    global _DEFAULT_IMPLEMENTATION  # noqa PLW0603
 
     if _DEFAULT_IMPLEMENTATION is not None:
         return _DEFAULT_IMPLEMENTATION
 
     try:
         implementation = next(all_implementations())
-    except StopIteration:  # pragma: no cover
+    except StopIteration:  # nocov
         logger.debug("Backend implementation import failed", exc_info=exc_info())
-        raise RuntimeError("No built-in server implementation installed.")
+        msg = "No built-in server implementation installed."
+        raise RuntimeError(msg) from None
     else:
         _DEFAULT_IMPLEMENTATION = implementation
         return implementation
diff --git a/src/reactpy/backend/fastapi.py b/src/py/reactpy/reactpy/backend/fastapi.py
similarity index 95%
rename from src/reactpy/backend/fastapi.py
rename to src/py/reactpy/reactpy/backend/fastapi.py
index c3cd038d8..575fce1fe 100644
--- a/src/reactpy/backend/fastapi.py
+++ b/src/py/reactpy/reactpy/backend/fastapi.py
@@ -2,8 +2,7 @@
 
 from fastapi import FastAPI
 
-from . import starlette
-
+from reactpy.backend import starlette
 
 serve_development_app = starlette.serve_development_app
 """Alias for :func:`reactpy.backend.starlette.serve_development_app`"""
diff --git a/src/reactpy/backend/flask.py b/src/py/reactpy/reactpy/backend/flask.py
similarity index 90%
rename from src/reactpy/backend/flask.py
rename to src/py/reactpy/reactpy/backend/flask.py
index 19dfbe1c3..e28ed7652 100644
--- a/src/reactpy/backend/flask.py
+++ b/src/py/reactpy/reactpy/backend/flask.py
@@ -9,7 +9,7 @@
 from queue import Queue as ThreadQueue
 from threading import Event as ThreadEvent
 from threading import Thread
-from typing import Any, Callable, NamedTuple, NoReturn, Optional, cast
+from typing import Any, Callable, NamedTuple, NoReturn, cast
 
 from flask import (
     Blueprint,
@@ -42,7 +42,6 @@
 from reactpy.core.types import ComponentType, RootComponentConstructor
 from reactpy.utils import Ref
 
-
 logger = logging.getLogger(__name__)
 
 
@@ -112,8 +111,9 @@ def run_server() -> None:
         # the thread should eventually join
         thread.join(timeout=3)
         # just double check it happened
-        if thread.is_alive():  # pragma: no cover
-            raise RuntimeError("Failed to shutdown server.")
+        if thread.is_alive():  # nocov
+            msg = "Failed to shutdown server."
+            raise RuntimeError(msg)
 
 
 def use_websocket() -> WebSocket:
@@ -129,11 +129,9 @@ def use_request() -> Request:
 def use_connection() -> Connection[_FlaskCarrier]:
     """Get the current :class:`Connection`"""
     conn = _use_connection()
-    if not isinstance(conn.carrier, _FlaskCarrier):  # pragma: no cover
-        raise TypeError(
-            f"Connection has unexpected carrier {conn.carrier}. "
-            "Are you running with a Flask server?"
-        )
+    if not isinstance(conn.carrier, _FlaskCarrier):  # nocov
+        msg = f"Connection has unexpected carrier {conn.carrier}. Are you running with a Flask server?"
+        raise TypeError(msg)
     return conn
 
 
@@ -154,7 +152,7 @@ def _setup_common_routes(
     options: Options,
 ) -> None:
     cors_options = options.cors
-    if cors_options:  # pragma: no cover
+    if cors_options:  # nocov
         cors_params = cors_options if isinstance(cors_options, dict) else {}
         CORS(api_blueprint, **cors_params)
 
@@ -204,20 +202,20 @@ def _dispatch_in_thread(
     path: str,
     component: ComponentType,
     send: Callable[[Any], None],
-    recv: Callable[[], Optional[Any]],
+    recv: Callable[[], Any | None],
 ) -> NoReturn:
     dispatch_thread_info_created = ThreadEvent()
-    dispatch_thread_info_ref: reactpy.Ref[
-        Optional[_DispatcherThreadInfo]
-    ] = reactpy.Ref(None)
+    dispatch_thread_info_ref: reactpy.Ref[_DispatcherThreadInfo | None] = reactpy.Ref(
+        None
+    )
 
     @copy_current_request_context
     def run_dispatcher() -> None:
         loop = asyncio.new_event_loop()
         asyncio.set_event_loop(loop)
 
-        thread_send_queue: "ThreadQueue[Any]" = ThreadQueue()
-        async_recv_queue: "AsyncQueue[Any]" = AsyncQueue()
+        thread_send_queue: ThreadQueue[Any] = ThreadQueue()
+        async_recv_queue: AsyncQueue[Any] = AsyncQueue()
 
         async def send_coro(value: Any) -> None:
             thread_send_queue.put(value)
@@ -258,7 +256,9 @@ async def main() -> None:
 
     dispatch_thread_info_created.wait()
     dispatch_thread_info = cast(_DispatcherThreadInfo, dispatch_thread_info_ref.current)
-    assert dispatch_thread_info is not None
+
+    if dispatch_thread_info is None:
+        raise RuntimeError("Failed to create dispatcher thread")  # nocov
 
     stop = ThreadEvent()
 
@@ -274,7 +274,7 @@ def run_send() -> None:
             dispatch_thread_info.dispatch_loop.call_soon_threadsafe(
                 dispatch_thread_info.async_recv_queue.put_nowait, value
             )
-    finally:  # pragma: no cover
+    finally:  # nocov
         dispatch_thread_info.dispatch_loop.call_soon_threadsafe(
             dispatch_thread_info.dispatch_future.cancel
         )
diff --git a/src/reactpy/backend/hooks.py b/src/py/reactpy/reactpy/backend/hooks.py
similarity index 75%
rename from src/reactpy/backend/hooks.py
rename to src/py/reactpy/reactpy/backend/hooks.py
index 981461316..19ad114ed 100644
--- a/src/reactpy/backend/hooks.py
+++ b/src/py/reactpy/reactpy/backend/hooks.py
@@ -1,12 +1,11 @@
 from __future__ import annotations
 
-from typing import Any, MutableMapping
+from collections.abc import MutableMapping
+from typing import Any
 
+from reactpy.backend.types import Connection, Location
 from reactpy.core.hooks import Context, create_context, use_context
 
-from .types import Connection, Location
-
-
 # backend implementations should establish this context at the root of an app
 ConnectionContext: Context[Connection[Any] | None] = create_context(None)
 
@@ -14,8 +13,9 @@
 def use_connection() -> Connection[Any]:
     """Get the current :class:`~reactpy.backend.types.Connection`."""
     conn = use_context(ConnectionContext)
-    if conn is None:
-        raise RuntimeError("No backend established a connection.")  # pragma: no cover
+    if conn is None:  # nocov
+        msg = "No backend established a connection."
+        raise RuntimeError(msg)
     return conn
 
 
diff --git a/src/reactpy/backend/sanic.py b/src/py/reactpy/reactpy/backend/sanic.py
similarity index 92%
rename from src/reactpy/backend/sanic.py
rename to src/py/reactpy/reactpy/backend/sanic.py
index 91d20b838..53dd0ce68 100644
--- a/src/reactpy/backend/sanic.py
+++ b/src/py/reactpy/reactpy/backend/sanic.py
@@ -4,7 +4,7 @@
 import json
 import logging
 from dataclasses import dataclass
-from typing import Any, Tuple
+from typing import Any
 from urllib import parse as urllib_parse
 from uuid import uuid4
 
@@ -13,12 +13,7 @@
 from sanic.server.websockets.connection import WebSocketConnection
 from sanic_cors import CORS
 
-from reactpy.backend.types import Connection, Location
-from reactpy.core.layout import Layout
-from reactpy.core.serve import RecvCoroutine, SendCoroutine, Stop, serve_layout
-from reactpy.core.types import RootComponentConstructor
-
-from ._common import (
+from reactpy.backend._common import (
     ASSETS_PATH,
     MODULES_PATH,
     PATH_PREFIX,
@@ -29,9 +24,12 @@
     safe_web_modules_dir_path,
     serve_development_asgi,
 )
-from .hooks import ConnectionContext
-from .hooks import use_connection as _use_connection
-
+from reactpy.backend.hooks import ConnectionContext
+from reactpy.backend.hooks import use_connection as _use_connection
+from reactpy.backend.types import Connection, Location
+from reactpy.core.layout import Layout
+from reactpy.core.serve import RecvCoroutine, SendCoroutine, Stop, serve_layout
+from reactpy.core.types import RootComponentConstructor
 
 logger = logging.getLogger(__name__)
 
@@ -82,11 +80,9 @@ def use_websocket() -> WebSocketConnection:
 def use_connection() -> Connection[_SanicCarrier]:
     """Get the current :class:`Connection`"""
     conn = _use_connection()
-    if not isinstance(conn.carrier, _SanicCarrier):  # pragma: no cover
-        raise TypeError(
-            f"Connection has unexpected carrier {conn.carrier}. "
-            "Are you running with a Sanic server?"
-        )
+    if not isinstance(conn.carrier, _SanicCarrier):  # nocov
+        msg = f"Connection has unexpected carrier {conn.carrier}. Are you running with a Sanic server?"
+        raise TypeError(msg)
     return conn
 
 
@@ -107,7 +103,7 @@ def _setup_common_routes(
     options: Options,
 ) -> None:
     cors_options = options.cors
-    if cors_options:  # pragma: no cover
+    if cors_options:  # nocov
         cors_params = cors_options if isinstance(cors_options, dict) else {}
         CORS(api_blueprint, **cors_params)
 
@@ -163,7 +159,7 @@ async def model_stream(
     ) -> None:
         asgi_app = getattr(request.app, "_asgi_app", None)
         scope = asgi_app.transport.scope if asgi_app else {}
-        if not scope:  # pragma: no cover
+        if not scope:  # nocov
             logger.warning("No scope. Sanic may not be running with an ASGI server")
 
         send, recv = _make_send_recv_callbacks(socket)
@@ -203,7 +199,7 @@ async def model_stream(
 
 def _make_send_recv_callbacks(
     socket: WebSocketConnection,
-) -> Tuple[SendCoroutine, RecvCoroutine]:
+) -> tuple[SendCoroutine, RecvCoroutine]:
     async def sock_send(value: Any) -> None:
         await socket.send(json.dumps(value))
 
diff --git a/src/reactpy/backend/starlette.py b/src/py/reactpy/reactpy/backend/starlette.py
similarity index 91%
rename from src/reactpy/backend/starlette.py
rename to src/py/reactpy/reactpy/backend/starlette.py
index 4ab16e91b..658fccfbd 100644
--- a/src/reactpy/backend/starlette.py
+++ b/src/py/reactpy/reactpy/backend/starlette.py
@@ -3,8 +3,9 @@
 import asyncio
 import json
 import logging
+from collections.abc import Awaitable
 from dataclasses import dataclass
-from typing import Any, Awaitable, Callable, Tuple
+from typing import Any, Callable
 
 from starlette.applications import Starlette
 from starlette.middleware.cors import CORSMiddleware
@@ -13,14 +14,7 @@
 from starlette.staticfiles import StaticFiles
 from starlette.websockets import WebSocket, WebSocketDisconnect
 
-from reactpy.backend.hooks import ConnectionContext
-from reactpy.backend.types import Connection, Location
-from reactpy.config import REACTPY_WEB_MODULES_DIR
-from reactpy.core.layout import Layout
-from reactpy.core.serve import RecvCoroutine, SendCoroutine, serve_layout
-from reactpy.core.types import RootComponentConstructor
-
-from ._common import (
+from reactpy.backend._common import (
     ASSETS_PATH,
     CLIENT_BUILD_DIR,
     MODULES_PATH,
@@ -29,9 +23,13 @@
     read_client_index_html,
     serve_development_asgi,
 )
-from .hooks import ConnectionContext
-from .hooks import use_connection as _use_connection
-
+from reactpy.backend.hooks import ConnectionContext
+from reactpy.backend.hooks import use_connection as _use_connection
+from reactpy.backend.types import Connection, Location
+from reactpy.config import REACTPY_WEB_MODULES_DIR
+from reactpy.core.layout import Layout
+from reactpy.core.serve import RecvCoroutine, SendCoroutine, serve_layout
+from reactpy.core.types import RootComponentConstructor
 
 logger = logging.getLogger(__name__)
 
@@ -78,11 +76,9 @@ def use_websocket() -> WebSocket:
 
 def use_connection() -> Connection[WebSocket]:
     conn = _use_connection()
-    if not isinstance(conn.carrier, WebSocket):  # pragma: no cover
-        raise TypeError(
-            f"Connection has unexpected carrier {conn.carrier}. "
-            "Are you running with a Flask server?"
-        )
+    if not isinstance(conn.carrier, WebSocket):  # nocov
+        msg = f"Connection has unexpected carrier {conn.carrier}. Are you running with a Flask server?"
+        raise TypeError(msg)
     return conn
 
 
@@ -99,7 +95,7 @@ class Options(CommonOptions):
 
 def _setup_common_routes(options: Options, app: Starlette) -> None:
     cors_options = options.cors
-    if cors_options:  # pragma: no cover
+    if cors_options:  # nocov
         cors_params = (
             cors_options if isinstance(cors_options, dict) else {"allow_origins": ["*"]}
         )
@@ -166,7 +162,7 @@ async def model_stream(socket: WebSocket) -> None:
 
 def _make_send_recv_callbacks(
     socket: WebSocket,
-) -> Tuple[SendCoroutine, RecvCoroutine]:
+) -> tuple[SendCoroutine, RecvCoroutine]:
     async def sock_send(value: Any) -> None:
         await socket.send_text(json.dumps(value))
 
diff --git a/src/reactpy/backend/tornado.py b/src/py/reactpy/reactpy/backend/tornado.py
similarity index 93%
rename from src/reactpy/backend/tornado.py
rename to src/py/reactpy/reactpy/backend/tornado.py
index b79f5fa0e..5ec877532 100644
--- a/src/reactpy/backend/tornado.py
+++ b/src/py/reactpy/reactpy/backend/tornado.py
@@ -16,13 +16,7 @@
 from tornado.wsgi import WSGIContainer
 from typing_extensions import TypeAlias
 
-from reactpy.backend.types import Connection, Location
-from reactpy.config import REACTPY_WEB_MODULES_DIR
-from reactpy.core.layout import Layout
-from reactpy.core.serve import serve_layout
-from reactpy.core.types import ComponentConstructor
-
-from ._common import (
+from reactpy.backend._common import (
     ASSETS_PATH,
     CLIENT_BUILD_DIR,
     MODULES_PATH,
@@ -30,9 +24,13 @@
     CommonOptions,
     read_client_index_html,
 )
-from .hooks import ConnectionContext
-from .hooks import use_connection as _use_connection
-
+from reactpy.backend.hooks import ConnectionContext
+from reactpy.backend.hooks import use_connection as _use_connection
+from reactpy.backend.types import Connection, Location
+from reactpy.config import REACTPY_WEB_MODULES_DIR
+from reactpy.core.layout import Layout
+from reactpy.core.serve import serve_layout
+from reactpy.core.types import ComponentConstructor
 
 Options = CommonOptions
 """Render server config for :func:`reactpy.backend.tornado.configure`"""
@@ -100,11 +98,9 @@ def use_request() -> HTTPServerRequest:
 
 def use_connection() -> Connection[HTTPServerRequest]:
     conn = _use_connection()
-    if not isinstance(conn.carrier, HTTPServerRequest):  # pragma: no cover
-        raise TypeError(
-            f"Connection has unexpected carrier {conn.carrier}. "
-            "Are you running with a Flask server?"
-        )
+    if not isinstance(conn.carrier, HTTPServerRequest):  # nocov
+        msg = f"Connection has unexpected carrier {conn.carrier}. Are you running with a Flask server?"
+        raise TypeError(msg)
     return conn
 
 
@@ -135,7 +131,7 @@ def _add_handler(
     app: Application, options: Options, handlers: _RouteHandlerSpecs
 ) -> None:
     prefixed_handlers: list[Any] = [
-        (urljoin(options.url_prefix, route_pattern),) + tuple(handler_info)
+        (urljoin(options.url_prefix, route_pattern), *tuple(handler_info))
         for route_pattern, *handler_info in handlers
     ]
     app.add_handlers(r".*", prefixed_handlers)
@@ -181,7 +177,7 @@ def initialize(
         self._url_prefix = url_prefix
 
     async def open(self, path: str = "", *args: Any, **kwargs: Any) -> None:
-        message_queue: "AsyncQueue[str]" = AsyncQueue()
+        message_queue: AsyncQueue[str] = AsyncQueue()
 
         async def send(value: Any) -> None:
             await self.write_message(json.dumps(value))
diff --git a/src/reactpy/backend/types.py b/src/py/reactpy/reactpy/backend/types.py
similarity index 93%
rename from src/reactpy/backend/types.py
rename to src/py/reactpy/reactpy/backend/types.py
index fb19aa5d0..fbc4addc0 100644
--- a/src/reactpy/backend/types.py
+++ b/src/py/reactpy/reactpy/backend/types.py
@@ -1,14 +1,12 @@
 from __future__ import annotations
 
 import asyncio
+from collections.abc import MutableMapping
 from dataclasses import dataclass
-from typing import Any, Callable, Generic, MutableMapping, TypeVar
-
-from typing_extensions import Protocol, runtime_checkable
+from typing import Any, Callable, Generic, Protocol, TypeVar, runtime_checkable
 
 from reactpy.core.types import RootComponentConstructor
 
-
 _App = TypeVar("_App")
 
 
diff --git a/src/reactpy/backend/utils.py b/src/py/reactpy/reactpy/backend/utils.py
similarity index 86%
rename from src/reactpy/backend/utils.py
rename to src/py/reactpy/reactpy/backend/utils.py
index b7576cfb0..3d9be13a4 100644
--- a/src/reactpy/backend/utils.py
+++ b/src/py/reactpy/reactpy/backend/utils.py
@@ -3,15 +3,14 @@
 import asyncio
 import logging
 import socket
+from collections.abc import Iterator
 from contextlib import closing
 from importlib import import_module
-from typing import Any, Iterator
+from typing import Any
 
+from reactpy.backend.types import BackendImplementation
 from reactpy.types import RootComponentConstructor
 
-from .types import BackendImplementation
-
-
 logger = logging.getLogger(__name__)
 
 SUPPORTED_PACKAGES = (
@@ -70,9 +69,8 @@ def find_available_port(
                 pass
             else:
                 return port
-    raise RuntimeError(
-        f"Host {host!r} has no available port in range {port_max}-{port_max}"
-    )
+    msg = f"Host {host!r} has no available port in range {port_max}-{port_max}"
+    raise RuntimeError(msg)
 
 
 def all_implementations() -> Iterator[BackendImplementation[Any]]:
@@ -81,12 +79,13 @@ def all_implementations() -> Iterator[BackendImplementation[Any]]:
         try:
             relative_import_name = f"{__name__.rsplit('.', 1)[0]}.{name}"
             module = import_module(relative_import_name)
-        except ImportError:  # pragma: no cover
+        except ImportError:  # nocov
             logger.debug(f"Failed to import {name!r}", exc_info=True)
             continue
 
-        if not isinstance(module, BackendImplementation):  # pragma: no cover
-            raise TypeError(f"{module.__name__!r} is an invalid implementation")
+        if not isinstance(module, BackendImplementation):  # nocov
+            msg = f"{module.__name__!r} is an invalid implementation"
+            raise TypeError(msg)
 
         yield module
 
diff --git a/src/reactpy/config.py b/src/py/reactpy/reactpy/config.py
similarity index 97%
rename from src/reactpy/config.py
rename to src/py/reactpy/reactpy/config.py
index 8f26271c4..ace93b6d2 100644
--- a/src/reactpy/config.py
+++ b/src/py/reactpy/reactpy/config.py
@@ -6,8 +6,7 @@
 from pathlib import Path
 from tempfile import TemporaryDirectory
 
-from ._option import Option as _Option
-
+from reactpy._option import Option as _Option
 
 REACTPY_DEBUG_MODE = _Option(
     "REACTPY_DEBUG_MODE",
diff --git a/src/reactpy/core/__init__.py b/src/py/reactpy/reactpy/core/__init__.py
similarity index 100%
rename from src/reactpy/core/__init__.py
rename to src/py/reactpy/reactpy/core/__init__.py
diff --git a/src/reactpy/core/_f_back.py b/src/py/reactpy/reactpy/core/_f_back.py
similarity index 73%
rename from src/reactpy/core/_f_back.py
rename to src/py/reactpy/reactpy/core/_f_back.py
index 81e66a4f1..fe1a6c10c 100644
--- a/src/reactpy/core/_f_back.py
+++ b/src/py/reactpy/reactpy/core/_f_back.py
@@ -7,9 +7,10 @@
 def f_module_name(index: int = 0) -> str:
     frame = f_back(index + 1)
     if frame is None:
-        return ""  # pragma: no cover
+        return ""  # nocov
     name = frame.f_globals.get("__name__", "")
-    assert isinstance(name, str), "Expected module name to be a string"
+    if not isinstance(name, str):
+        raise TypeError("Expected module name to be a string")  # nocov
     return name
 
 
@@ -20,4 +21,4 @@ def f_back(index: int = 0) -> FrameType | None:
             return frame
         frame = frame.f_back
         index -= 1
-    return None  # pragma: no cover
+    return None  # nocov
diff --git a/src/reactpy/core/_thread_local.py b/src/py/reactpy/reactpy/core/_thread_local.py
similarity index 99%
rename from src/reactpy/core/_thread_local.py
rename to src/py/reactpy/reactpy/core/_thread_local.py
index 80a42d069..b3d6a14b0 100644
--- a/src/reactpy/core/_thread_local.py
+++ b/src/py/reactpy/reactpy/core/_thread_local.py
@@ -2,7 +2,6 @@
 from typing import Callable, Generic, TypeVar
 from weakref import WeakKeyDictionary
 
-
 _StateType = TypeVar("_StateType")
 
 
diff --git a/src/reactpy/core/component.py b/src/py/reactpy/reactpy/core/component.py
similarity index 80%
rename from src/reactpy/core/component.py
rename to src/py/reactpy/reactpy/core/component.py
index 79731ad4e..f825aac71 100644
--- a/src/reactpy/core/component.py
+++ b/src/py/reactpy/reactpy/core/component.py
@@ -2,9 +2,9 @@
 
 import inspect
 from functools import wraps
-from typing import Any, Callable, Dict, Optional, Tuple
+from typing import Any, Callable
 
-from .types import ComponentType, VdomDict
+from reactpy.core.types import ComponentType, VdomDict
 
 
 def component(
@@ -21,12 +21,11 @@ def component(
         inspect.Parameter.KEYWORD_ONLY,
         inspect.Parameter.POSITIONAL_OR_KEYWORD,
     ):
-        raise TypeError(
-            f"Component render function {function} uses reserved parameter 'key'"
-        )
+        msg = f"Component render function {function} uses reserved parameter 'key'"
+        raise TypeError(msg)
 
     @wraps(function)
-    def constructor(*args: Any, key: Optional[Any] = None, **kwargs: Any) -> Component:
+    def constructor(*args: Any, key: Any | None = None, **kwargs: Any) -> Component:
         return Component(function, key, args, kwargs, sig)
 
     return constructor
@@ -40,9 +39,9 @@ class Component:
     def __init__(
         self,
         function: Callable[..., ComponentType | VdomDict | str | None],
-        key: Optional[Any],
-        args: Tuple[Any, ...],
-        kwargs: Dict[str, Any],
+        key: Any | None,
+        args: tuple[Any, ...],
+        kwargs: dict[str, Any],
         sig: inspect.Signature,
     ) -> None:
         self.key = key
diff --git a/src/reactpy/core/events.py b/src/py/reactpy/reactpy/core/events.py
similarity index 93%
rename from src/reactpy/core/events.py
rename to src/py/reactpy/reactpy/core/events.py
index 4cad9fd7f..51fe8d0d3 100644
--- a/src/reactpy/core/events.py
+++ b/src/py/reactpy/reactpy/core/events.py
@@ -1,10 +1,10 @@
 from __future__ import annotations
 
 import asyncio
-from typing import Any, Callable, Optional, Sequence, overload
+from collections.abc import Sequence
+from typing import Any, Callable, Literal, overload
 
 from anyio import create_task_group
-from typing_extensions import Literal
 
 from reactpy.core.types import EventHandlerFunc, EventHandlerType
 
@@ -104,7 +104,7 @@ def __init__(
         function: EventHandlerFunc,
         stop_propagation: bool = False,
         prevent_default: bool = False,
-        target: Optional[str] = None,
+        target: str | None = None,
     ) -> None:
         self.function = to_event_handler_function(function, positional_args=False)
         self.prevent_default = prevent_default
@@ -174,7 +174,8 @@ def merge_event_handlers(
     :attr:`~reactpy.core.proto.EventHandlerType.prevent_default` attributes.
     """
     if not event_handlers:
-        raise ValueError("No event handlers to merge")
+        msg = "No event handlers to merge"
+        raise ValueError(msg)
     elif len(event_handlers) == 1:
         return event_handlers[0]
 
@@ -190,10 +191,8 @@ def merge_event_handlers(
             or handler.prevent_default != prevent_default
             or handler.target != target
         ):
-            raise ValueError(
-                "Cannot merge handlers - "
-                "'stop_propagation', 'prevent_default' or 'target' mistmatch."
-            )
+            msg = "Cannot merge handlers - 'stop_propagation', 'prevent_default' or 'target' mistmatch."
+            raise ValueError(msg)
 
     return EventHandler(
         merge_event_handler_funcs([h.function for h in event_handlers]),
@@ -208,7 +207,8 @@ def merge_event_handler_funcs(
 ) -> EventHandlerFunc:
     """Make one event handler function from many"""
     if not functions:
-        raise ValueError("No event handler functions to merge")
+        msg = "No event handler functions to merge"
+        raise ValueError(msg)
     elif len(functions) == 1:
         return functions[0]
 
diff --git a/src/reactpy/core/hooks.py b/src/py/reactpy/reactpy/core/hooks.py
similarity index 96%
rename from src/reactpy/core/hooks.py
rename to src/py/reactpy/reactpy/core/hooks.py
index c40a6869f..9b025c0ca 100644
--- a/src/reactpy/core/hooks.py
+++ b/src/py/reactpy/reactpy/core/hooks.py
@@ -1,30 +1,28 @@
 from __future__ import annotations
 
 import asyncio
+from collections.abc import Awaitable, Sequence
 from logging import getLogger
 from types import FunctionType
 from typing import (
     TYPE_CHECKING,
     Any,
-    Awaitable,
     Callable,
     Generic,
     NewType,
-    Sequence,
+    Protocol,
     TypeVar,
     cast,
     overload,
 )
 
-from typing_extensions import Protocol, TypeAlias
+from typing_extensions import TypeAlias
 
 from reactpy.config import REACTPY_DEBUG_MODE
+from reactpy.core._thread_local import ThreadLocal
+from reactpy.core.types import ComponentType, Key, State, VdomDict
 from reactpy.utils import Ref
 
-from ._thread_local import ThreadLocal
-from .types import ComponentType, Key, State, VdomDict
-
-
 if not TYPE_CHECKING:
     # make flake8 think that this variable exists
     ellipsis = type(...)
@@ -167,8 +165,6 @@ def effect() -> None:
             if clean is not None:
                 hook.add_effect(COMPONENT_WILL_UNMOUNT_EFFECT, clean)
 
-            return None
-
         return memoize(lambda: hook.add_effect(LAYOUT_DID_RENDER_EFFECT, effect))
 
     if function is not None:
@@ -250,13 +246,13 @@ def use_context(context: Context[_Type]) -> _Type:
     provider = hook.get_context_provider(context)
 
     if provider is None:
-        # force type checker to realize this is just a normal function
-        assert isinstance(context, FunctionType), f"{context} is not a Context"
-        # __kwdefault__ can be None if no kwarg only parameters exist
-        assert context.__kwdefaults__ is not None, f"{context} has no 'value' kwarg"
-        # lastly check that 'value' kwarg exists
-        assert "value" in context.__kwdefaults__, f"{context} has no 'value' kwarg"
-        # then we can safely access the context's default value
+        # same assertions but with normal exceptions
+        if not isinstance(context, FunctionType):
+            raise TypeError(f"{context} is not a Context")  # nocov
+        if context.__kwdefaults__ is None:
+            raise TypeError(f"{context} has no 'value' kwarg")  # nocov
+        if "value" not in context.__kwdefaults__:
+            raise TypeError(f"{context} has no 'value' kwarg")  # nocov
         return cast(_Type, context.__kwdefaults__["value"])
 
     return provider._value
@@ -459,7 +455,7 @@ class _Memo(Generic[_Type]):
 
     def empty(self) -> bool:
         try:
-            self.value
+            self.value  # noqa: B018
         except AttributeError:
             return True
         else:
@@ -627,7 +623,6 @@ def schedule_render(self) -> None:
             self._schedule_render_later = True
         else:
             self._schedule_render()
-        return None
 
     def use_state(self, function: Callable[[], _Type]) -> _Type:
         if not self._rendered_atleast_once:
@@ -713,8 +708,8 @@ def set_current(self) -> None:
 
     def unset_current(self) -> None:
         """Unset this hook as the active hook in this thread"""
-        # this assertion should never fail - primarilly useful for debug
-        assert _hook_stack.get().pop() is self
+        if _hook_stack.get().pop() is not self:
+            raise RuntimeError("Hook stack is in an invalid state")  # nocov
 
     def _schedule_render(self) -> None:
         try:
diff --git a/src/reactpy/core/layout.py b/src/py/reactpy/reactpy/core/layout.py
similarity index 92%
rename from src/reactpy/core/layout.py
rename to src/py/reactpy/reactpy/core/layout.py
index 2b785815e..368e79c7d 100644
--- a/src/reactpy/core/layout.py
+++ b/src/py/reactpy/reactpy/core/layout.py
@@ -3,16 +3,15 @@
 import abc
 import asyncio
 from collections import Counter
+from collections.abc import Iterator
 from contextlib import ExitStack
 from logging import getLogger
 from typing import (
     Any,
     Callable,
     Generic,
-    Iterator,
     NamedTuple,
     NewType,
-    Optional,
     TypeVar,
     cast,
 )
@@ -20,10 +19,8 @@
 from weakref import ref as weakref
 
 from reactpy.config import REACTPY_CHECK_VDOM_SPEC, REACTPY_DEBUG_MODE
-from reactpy.utils import Ref
-
-from .hooks import LifeCycleHook
-from .types import (
+from reactpy.core.hooks import LifeCycleHook
+from reactpy.core.types import (
     ComponentType,
     EventHandlerDict,
     LayoutEventMessage,
@@ -31,8 +28,8 @@
     VdomDict,
     VdomJson,
 )
-from .vdom import validate_vdom_json
-
+from reactpy.core.vdom import validate_vdom_json
+from reactpy.utils import Ref
 
 logger = getLogger(__name__)
 
@@ -48,13 +45,14 @@ class Layout:
         "_model_states_by_life_cycle_state_id",
     ]
 
-    if not hasattr(abc.ABC, "__weakref__"):  # pragma: no cover
+    if not hasattr(abc.ABC, "__weakref__"):  # nocov
         __slots__.append("__weakref__")
 
-    def __init__(self, root: "ComponentType") -> None:
+    def __init__(self, root: ComponentType) -> None:
         super().__init__()
         if not isinstance(root, ComponentType):
-            raise TypeError(f"Expected a ComponentType, not {type(root)!r}.")
+            msg = f"Expected a ComponentType, not {type(root)!r}."
+            raise TypeError(msg)
         self.root = root
 
     async def __aenter__(self) -> Layout:
@@ -82,8 +80,6 @@ async def __aexit__(self, *exc: Any) -> None:
         del self._root_life_cycle_state_id
         del self._model_states_by_life_cycle_state_id
 
-        return None
-
     async def deliver(self, event: LayoutEventMessage) -> None:
         """Dispatch an event to the targeted handler"""
         # It is possible for an element in the frontend to produce an event
@@ -138,7 +134,7 @@ def _create_layout_update(self, old_state: _ModelState) -> LayoutUpdateMessage:
     def _render_component(
         self,
         exit_stack: ExitStack,
-        old_state: Optional[_ModelState],
+        old_state: _ModelState | None,
         new_state: _ModelState,
         component: ComponentType,
     ) -> None:
@@ -195,14 +191,15 @@ def _render_component(
     def _render_model(
         self,
         exit_stack: ExitStack,
-        old_state: Optional[_ModelState],
+        old_state: _ModelState | None,
         new_state: _ModelState,
         raw_model: Any,
     ) -> None:
         try:
             new_state.model.current = {"tagName": raw_model["tagName"]}
-        except Exception as e:  # pragma: no cover
-            raise ValueError(f"Expected a VDOM element dict, not {raw_model}") from e
+        except Exception as e:  # nocov
+            msg = f"Expected a VDOM element dict, not {raw_model}"
+            raise ValueError(msg) from e
         if "key" in raw_model:
             new_state.key = new_state.model.current["key"] = raw_model["key"]
         if "importSource" in raw_model:
@@ -214,7 +211,7 @@ def _render_model(
 
     def _render_model_attributes(
         self,
-        old_state: Optional[_ModelState],
+        old_state: _ModelState | None,
         new_state: _ModelState,
         raw_model: dict[str, Any],
     ) -> None:
@@ -278,7 +275,7 @@ def _render_model_event_handlers_without_old_state(
     def _render_model_children(
         self,
         exit_stack: ExitStack,
-        old_state: Optional[_ModelState],
+        old_state: _ModelState | None,
         new_state: _ModelState,
         raw_children: Any,
     ) -> None:
@@ -301,9 +298,8 @@ def _render_model_children(
         if len(new_keys) != len(raw_children):
             key_counter = Counter(item[2] for item in child_type_key_tuples)
             duplicate_keys = [key for key, count in key_counter.items() if count > 1]
-            raise ValueError(
-                f"Duplicate keys {duplicate_keys} at {new_state.patch_path or '/'!r}"
-            )
+            msg = f"Duplicate keys {duplicate_keys} at {new_state.patch_path or '/'!r}"
+            raise ValueError(msg)
 
         old_keys = set(old_state.children_by_key).difference(new_keys)
         if old_keys:
@@ -322,21 +318,20 @@ def _render_model_children(
                         index,
                         key,
                     )
+                elif old_child_state.is_component_state:
+                    self._unmount_model_states([old_child_state])
+                    new_child_state = _make_element_model_state(
+                        new_state,
+                        index,
+                        key,
+                    )
+                    old_child_state = None
                 else:
-                    if old_child_state.is_component_state:
-                        self._unmount_model_states([old_child_state])
-                        new_child_state = _make_element_model_state(
-                            new_state,
-                            index,
-                            key,
-                        )
-                        old_child_state = None
-                    else:
-                        new_child_state = _update_element_model_state(
-                            old_child_state,
-                            new_state,
-                            index,
-                        )
+                    new_child_state = _update_element_model_state(
+                        old_child_state,
+                        new_state,
+                        index,
+                    )
                 self._render_model(exit_stack, old_child_state, new_child_state, child)
                 new_state.append_child(new_child_state.model.current)
                 new_state.children_by_key[key] = new_child_state
@@ -392,9 +387,8 @@ def _render_model_children_without_old_state(
         if len(new_keys) != len(raw_children):
             key_counter = Counter(item[2] for item in child_type_key_tuples)
             duplicate_keys = [key for key, count in key_counter.items() if count > 1]
-            raise ValueError(
-                f"Duplicate keys {duplicate_keys} at {new_state.patch_path or '/'!r}"
-            )
+            msg = f"Duplicate keys {duplicate_keys} at {new_state.patch_path or '/'!r}"
+            raise ValueError(msg)
 
         new_state.model.current["children"] = []
         for index, (child, child_type, key) in enumerate(child_type_key_tuples):
@@ -467,7 +461,7 @@ def _make_component_model_state(
 def _copy_component_model_state(old_model_state: _ModelState) -> _ModelState:
     # use try/except here because not having a parent is rare (only the root state)
     try:
-        parent: Optional[_ModelState] = old_model_state.parent
+        parent: _ModelState | None = old_model_state.parent
     except AttributeError:
         parent = None
 
@@ -555,14 +549,14 @@ class _ModelState:
 
     def __init__(
         self,
-        parent: Optional[_ModelState],
+        parent: _ModelState | None,
         index: int,
         key: Any,
         model: Ref[VdomJson],
         patch_path: str,
         children_by_key: dict[str, _ModelState],
         targets_by_event: dict[str, str],
-        life_cycle_state: Optional[_LifeCycleState] = None,
+        life_cycle_state: _LifeCycleState | None = None,
     ):
         self.index = index
         """The index of the element amongst its siblings"""
@@ -600,13 +594,14 @@ def is_component_state(self) -> bool:
     @property
     def parent(self) -> _ModelState:
         parent = self._parent_ref()
-        assert parent is not None, "detached model state"
+        if parent is None:
+            raise RuntimeError("detached model state")  # nocov
         return parent
 
     def append_child(self, child: Any) -> None:
         self.model.current["children"].append(child)
 
-    def __repr__(self) -> str:  # pragma: no cover
+    def __repr__(self) -> str:  # nocov
         return f"ModelState({ {s: getattr(self, s, None) for s in self.__slots__} })"
 
 
@@ -665,7 +660,6 @@ def put(self, value: _Type) -> None:
         if value not in self._pending:
             self._pending.add(value)
             self._loop.call_soon_threadsafe(self._queue.put_nowait, value)
-        return None
 
     async def get(self) -> _Type:
         while True:
diff --git a/src/reactpy/core/serve.py b/src/py/reactpy/reactpy/core/serve.py
similarity index 85%
rename from src/reactpy/core/serve.py
rename to src/py/reactpy/reactpy/core/serve.py
index 2802df9e2..61a7e4ce6 100644
--- a/src/reactpy/core/serve.py
+++ b/src/py/reactpy/reactpy/core/serve.py
@@ -1,14 +1,14 @@
 from __future__ import annotations
 
-from asyncio import create_task
+from collections.abc import Awaitable
 from logging import getLogger
-from typing import Awaitable, Callable
+from typing import Callable
 
 from anyio import create_task_group
+from anyio.abc import TaskGroup
 
 from reactpy.core.types import LayoutEventMessage, LayoutType, LayoutUpdateMessage
 
-
 logger = getLogger(__name__)
 
 
@@ -40,7 +40,7 @@ async def serve_layout(
         try:
             async with create_task_group() as task_group:
                 task_group.start_soon(_single_outgoing_loop, layout, send)
-                task_group.start_soon(_single_incoming_loop, layout, recv)
+                task_group.start_soon(_single_incoming_loop, task_group, layout, recv)
         except Stop:
             logger.info(f"Stopped serving {layout}")
 
@@ -53,9 +53,11 @@ async def _single_outgoing_loop(
 
 
 async def _single_incoming_loop(
-    layout: LayoutType[LayoutUpdateMessage, LayoutEventMessage], recv: RecvCoroutine
+    task_group: TaskGroup,
+    layout: LayoutType[LayoutUpdateMessage, LayoutEventMessage],
+    recv: RecvCoroutine,
 ) -> None:
     while True:
         # We need to fire and forget here so that we avoid waiting on the completion
         # of this event handler before receiving and running the next one.
-        create_task(layout.deliver(await recv()))
+        task_group.start_soon(layout.deliver, await recv())
diff --git a/src/reactpy/core/types.py b/src/py/reactpy/reactpy/core/types.py
similarity index 90%
rename from src/reactpy/core/types.py
rename to src/py/reactpy/reactpy/core/types.py
index e9ec39c1a..93f4fe36b 100644
--- a/src/reactpy/core/types.py
+++ b/src/py/reactpy/reactpy/core/types.py
@@ -2,22 +2,22 @@
 
 import sys
 from collections import namedtuple
-from collections.abc import Sequence
+from collections.abc import Mapping, Sequence
 from types import TracebackType
 from typing import (
     TYPE_CHECKING,
     Any,
     Callable,
     Generic,
-    Mapping,
+    Literal,
     NamedTuple,
-    Type,
+    Protocol,
     TypeVar,
     overload,
+    runtime_checkable,
 )
 
-from typing_extensions import Literal, Protocol, TypeAlias, TypedDict, runtime_checkable
-
+from typing_extensions import TypeAlias, TypedDict
 
 _Type = TypeVar("_Type")
 
@@ -28,7 +28,7 @@ class State(NamedTuple, Generic[_Type]):
         value: _Type
         set_value: Callable[[_Type | Callable[[_Type], _Type]], None]
 
-else:  # pragma: no cover
+else:  # nocov
     State = namedtuple("State", ("value", "set_value"))
 
 
@@ -81,7 +81,7 @@ async def __aenter__(self) -> LayoutType[_Render, _Event]:
 
     async def __aexit__(
         self,
-        exc_type: Type[Exception],
+        exc_type: type[Exception],
         exc_value: Exception,
         traceback: TracebackType,
     ) -> bool | None:
@@ -109,12 +109,12 @@ class _VdomDictOptional(TypedDict, total=False):
         | Any
     ]
     attributes: VdomAttributes
-    eventHandlers: EventHandlerDict  # noqa
-    importSource: ImportSourceDict  # noqa
+    eventHandlers: EventHandlerDict
+    importSource: ImportSourceDict
 
 
 class _VdomDictRequired(TypedDict, total=True):
-    tagName: str  # noqa
+    tagName: str
 
 
 class VdomDict(_VdomDictRequired, _VdomDictOptional):
@@ -124,8 +124,8 @@ class VdomDict(_VdomDictRequired, _VdomDictOptional):
 class ImportSourceDict(TypedDict):
     source: str
     fallback: Any
-    sourceType: str  # noqa
-    unmountBeforeUpdate: bool  # noqa
+    sourceType: str
+    unmountBeforeUpdate: bool
 
 
 class _OptionalVdomJson(TypedDict, total=False):
@@ -133,12 +133,12 @@ class _OptionalVdomJson(TypedDict, total=False):
     error: str
     children: list[Any]
     attributes: dict[str, Any]
-    eventHandlers: dict[str, _JsonEventTarget]  # noqa
-    importSource: _JsonImportSource  # noqa
+    eventHandlers: dict[str, _JsonEventTarget]
+    importSource: _JsonImportSource
 
 
 class _RequiredVdomJson(TypedDict, total=True):
-    tagName: str  # noqa
+    tagName: str
 
 
 class VdomJson(_RequiredVdomJson, _OptionalVdomJson):
@@ -147,8 +147,8 @@ class VdomJson(_RequiredVdomJson, _OptionalVdomJson):
 
 class _JsonEventTarget(TypedDict):
     target: str
-    preventDefault: bool  # noqa
-    stopPropagation: bool  # noqa
+    preventDefault: bool
+    stopPropagation: bool
 
 
 class _JsonImportSource(TypedDict):
diff --git a/src/reactpy/core/vdom.py b/src/py/reactpy/reactpy/core/vdom.py
similarity index 96%
rename from src/reactpy/core/vdom.py
rename to src/py/reactpy/reactpy/core/vdom.py
index 9579560b9..c716fe347 100644
--- a/src/reactpy/core/vdom.py
+++ b/src/py/reactpy/reactpy/core/vdom.py
@@ -1,14 +1,15 @@
 from __future__ import annotations
 
 import logging
+from collections.abc import Mapping, Sequence
 from functools import wraps
-from typing import Any, Mapping, Sequence, cast, overload
+from typing import Any, Protocol, cast, overload
 
 from fastjsonschema import compile as compile_json_schema
-from typing_extensions import Protocol
 
 from reactpy._warnings import warn
 from reactpy.config import REACTPY_DEBUG_MODE
+from reactpy.core._f_back import f_module_name
 from reactpy.core.events import EventHandler, to_event_handler_function
 from reactpy.core.types import (
     ComponentType,
@@ -24,9 +25,6 @@
     VdomJson,
 )
 
-from ._f_back import f_module_name
-
-
 logger = logging.getLogger()
 
 
@@ -164,7 +162,7 @@ def vdom(
             (subject to change) specifies javascript that, when evaluated returns a
             React component.
     """
-    if kwargs:  # pragma: no cover
+    if kwargs:  # nocov
         if "key" in kwargs:
             if attributes_and_children:
                 maybe_attributes, *children = attributes_and_children
@@ -188,7 +186,8 @@ def vdom(
             )
 
         if kwargs:
-            raise ValueError(f"Extra keyword arguments {kwargs}")
+            msg = f"Extra keyword arguments {kwargs}"
+            raise ValueError(msg)
 
     model: VdomDict = {"tagName": tag}
 
@@ -226,7 +225,8 @@ def make_vdom_constructor(
     def constructor(*attributes_and_children: Any, **kwargs: Any) -> VdomDict:
         model = vdom(tag, *attributes_and_children, **kwargs)
         if not allow_children and "children" in model:
-            raise TypeError(f"{tag!r} nodes cannot have children.")
+            msg = f"{tag!r} nodes cannot have children."
+            raise TypeError(msg)
         if import_source:
             model["importSource"] = import_source
         return model
@@ -308,7 +308,7 @@ def separate_attributes_and_event_handlers(
 
         separated_event_handlers[k] = handler
 
-    return separated_attributes, {k: h for k, h in separated_event_handlers.items()}
+    return separated_attributes, dict(separated_event_handlers.items())
 
 
 def _is_attributes(value: Any) -> bool:
diff --git a/src/reactpy/future.py b/src/py/reactpy/reactpy/future.py
similarity index 100%
rename from src/reactpy/future.py
rename to src/py/reactpy/reactpy/future.py
diff --git a/src/reactpy/html.py b/src/py/reactpy/reactpy/html.py
similarity index 95%
rename from src/reactpy/html.py
rename to src/py/reactpy/reactpy/html.py
index 5c1d8b6ad..0387b3135 100644
--- a/src/reactpy/html.py
+++ b/src/py/reactpy/reactpy/html.py
@@ -158,7 +158,7 @@
 
 from __future__ import annotations
 
-from typing import Sequence
+from collections.abc import Sequence
 
 from reactpy.core.types import (
     EventHandlerDict,
@@ -169,7 +169,6 @@
 )
 from reactpy.core.vdom import custom_vdom_constructor, make_vdom_constructor
 
-
 __all__ = (
     "_",
     "a",
@@ -293,7 +292,8 @@ def _fragment(
 ) -> VdomDict:
     """An HTML fragment - this element will not appear in the DOM"""
     if attributes or event_handlers:
-        raise TypeError("Fragments cannot have attributes besides 'key'")
+        msg = "Fragments cannot have attributes besides 'key'"
+        raise TypeError(msg)
     model: VdomDict = {"tagName": ""}
 
     if children:
@@ -382,14 +382,14 @@ def _fragment(
 area = make_vdom_constructor("area", allow_children=False)
 audio = make_vdom_constructor("audio")
 img = make_vdom_constructor("img", allow_children=False)
-map = make_vdom_constructor("map")
+map = make_vdom_constructor("map")  # noqa: A001
 track = make_vdom_constructor("track")
 video = make_vdom_constructor("video")
 
 # Embedded content
 embed = make_vdom_constructor("embed", allow_children=False)
 iframe = make_vdom_constructor("iframe", allow_children=False)
-object = make_vdom_constructor("object")
+object = make_vdom_constructor("object")  # noqa: A001
 param = make_vdom_constructor("param")
 picture = make_vdom_constructor("picture")
 portal = make_vdom_constructor("portal", allow_children=False)
@@ -473,13 +473,16 @@ def _script(
     model: VdomDict = {"tagName": "script"}
 
     if event_handlers:
-        raise ValueError("'script' elements do not support event handlers")
+        msg = "'script' elements do not support event handlers"
+        raise ValueError(msg)
 
     if children:
         if len(children) > 1:
-            raise ValueError("'script' nodes may have, at most, one child.")
+            msg = "'script' nodes may have, at most, one child."
+            raise ValueError(msg)
         elif not isinstance(children[0], str):
-            raise ValueError("The child of a 'script' must be a string.")
+            msg = "The child of a 'script' must be a string."
+            raise ValueError(msg)
         else:
             model["children"] = children
             if key is None:
@@ -519,7 +522,7 @@ def _script(
 button = make_vdom_constructor("button")
 fieldset = make_vdom_constructor("fieldset")
 form = make_vdom_constructor("form")
-input = make_vdom_constructor("input", allow_children=False)
+input = make_vdom_constructor("input", allow_children=False)  # noqa: A001
 label = make_vdom_constructor("label")
 legend = make_vdom_constructor("legend")
 meter = make_vdom_constructor("meter")
diff --git a/src/reactpy/logging.py b/src/py/reactpy/reactpy/logging.py
similarity index 95%
rename from src/reactpy/logging.py
rename to src/py/reactpy/reactpy/logging.py
index 1a07984a1..f10414cb6 100644
--- a/src/reactpy/logging.py
+++ b/src/py/reactpy/reactpy/logging.py
@@ -2,8 +2,7 @@
 import sys
 from logging.config import dictConfig
 
-from .config import REACTPY_DEBUG_MODE
-
+from reactpy.config import REACTPY_DEBUG_MODE
 
 dictConfig(
     {
diff --git a/src/reactpy/py.typed b/src/py/reactpy/reactpy/py.typed
similarity index 100%
rename from src/reactpy/py.typed
rename to src/py/reactpy/reactpy/py.typed
diff --git a/src/reactpy/sample.py b/src/py/reactpy/reactpy/sample.py
similarity index 81%
rename from src/reactpy/sample.py
rename to src/py/reactpy/reactpy/sample.py
index 7905c1586..8509c773d 100644
--- a/src/reactpy/sample.py
+++ b/src/py/reactpy/reactpy/sample.py
@@ -1,8 +1,8 @@
 from __future__ import annotations
 
-from . import html
-from .core.component import component
-from .core.types import VdomDict
+from reactpy import html
+from reactpy.core.component import component
+from reactpy.core.types import VdomDict
 
 
 @component
diff --git a/src/reactpy/svg.py b/src/py/reactpy/reactpy/svg.py
similarity index 97%
rename from src/reactpy/svg.py
rename to src/py/reactpy/reactpy/svg.py
index ef9995468..ebfe58ee6 100644
--- a/src/reactpy/svg.py
+++ b/src/py/reactpy/reactpy/svg.py
@@ -1,6 +1,5 @@
 from reactpy.core.vdom import make_vdom_constructor
 
-
 __all__ = (
     "a",
     "animate",
@@ -107,7 +106,7 @@
 fe_spot_light = make_vdom_constructor("feSpotLight", allow_children=False)
 fe_tile = make_vdom_constructor("feTile", allow_children=False)
 fe_turbulence = make_vdom_constructor("feTurbulence", allow_children=False)
-filter = make_vdom_constructor("filter", allow_children=False)
+filter = make_vdom_constructor("filter", allow_children=False)  # noqa: A001
 foreign_object = make_vdom_constructor("foreignObject", allow_children=False)
 g = make_vdom_constructor("g")
 hatch = make_vdom_constructor("hatch", allow_children=False)
@@ -126,7 +125,7 @@
 radial_gradient = make_vdom_constructor("radialGradient", allow_children=False)
 rect = make_vdom_constructor("rect", allow_children=False)
 script = make_vdom_constructor("script", allow_children=False)
-set = make_vdom_constructor("set", allow_children=False)
+set = make_vdom_constructor("set", allow_children=False)  # noqa: A001
 stop = make_vdom_constructor("stop", allow_children=False)
 style = make_vdom_constructor("style", allow_children=False)
 svg = make_vdom_constructor("svg")
diff --git a/src/reactpy/testing/__init__.py b/src/py/reactpy/reactpy/testing/__init__.py
similarity index 59%
rename from src/reactpy/testing/__init__.py
rename to src/py/reactpy/reactpy/testing/__init__.py
index 1c9fcda1c..9f61cec57 100644
--- a/src/reactpy/testing/__init__.py
+++ b/src/py/reactpy/reactpy/testing/__init__.py
@@ -1,14 +1,18 @@
-from .backend import BackendFixture
-from .common import HookCatcher, StaticEventHandler, clear_reactpy_web_modules_dir, poll
-from .display import DisplayFixture
-from .logs import (
+from reactpy.testing.backend import BackendFixture
+from reactpy.testing.common import (
+    HookCatcher,
+    StaticEventHandler,
+    clear_reactpy_web_modules_dir,
+    poll,
+)
+from reactpy.testing.display import DisplayFixture
+from reactpy.testing.logs import (
     LogAssertionError,
     assert_reactpy_did_log,
     assert_reactpy_did_not_log,
     capture_reactpy_logs,
 )
 
-
 __all__ = [
     "assert_reactpy_did_not_log",
     "assert_reactpy_did_log",
diff --git a/src/reactpy/testing/backend.py b/src/py/reactpy/reactpy/testing/backend.py
similarity index 87%
rename from src/reactpy/testing/backend.py
rename to src/py/reactpy/reactpy/testing/backend.py
index 5749e1fef..8e4042284 100644
--- a/src/reactpy/testing/backend.py
+++ b/src/py/reactpy/reactpy/testing/backend.py
@@ -4,7 +4,7 @@
 import logging
 from contextlib import AsyncExitStack
 from types import TracebackType
-from typing import Any, Callable, Optional, Tuple, Type, Union
+from typing import Any, Callable
 from urllib.parse import urlencode, urlunparse
 
 from reactpy.backend import default as default_server
@@ -14,10 +14,13 @@
 from reactpy.core.component import component
 from reactpy.core.hooks import use_callback, use_effect, use_state
 from reactpy.core.types import ComponentConstructor
+from reactpy.testing.logs import (
+    LogAssertionError,
+    capture_reactpy_logs,
+    list_logged_exceptions,
+)
 from reactpy.utils import Ref
 
-from .logs import LogAssertionError, capture_reactpy_logs, list_logged_exceptions
-
 
 class BackendFixture:
     """A test fixture for running a server and imperatively displaying views
@@ -38,7 +41,7 @@ class BackendFixture:
     def __init__(
         self,
         host: str = "127.0.0.1",
-        port: Optional[int] = None,
+        port: int | None = None,
         app: Any | None = None,
         implementation: BackendImplementation[Any] | None = None,
         options: Any | None = None,
@@ -53,10 +56,8 @@ def __init__(
 
         if app is not None:
             if implementation is None:
-                raise ValueError(
-                    "If an application instance its corresponding "
-                    "server implementation must be provided too."
-                )
+                msg = "If an application instance its corresponding server implementation must be provided too."
+                raise ValueError(msg)
 
         self._app = app
         self.implementation = implementation or default_server
@@ -67,7 +68,7 @@ def log_records(self) -> list[logging.LogRecord]:
         """A list of captured log records"""
         return self._records
 
-    def url(self, path: str = "", query: Optional[Any] = None) -> str:
+    def url(self, path: str = "", query: Any | None = None) -> str:
         """Return a URL string pointing to the host and point of the server
 
         Args:
@@ -88,7 +89,7 @@ def url(self, path: str = "", query: Optional[Any] = None) -> str:
     def list_logged_exceptions(
         self,
         pattern: str = "",
-        types: Union[Type[Any], Tuple[Type[Any], ...]] = Exception,
+        types: type[Any] | tuple[type[Any], ...] = Exception,
         log_level: int = logging.ERROR,
         del_log_records: bool = True,
     ) -> list[BaseException]:
@@ -132,7 +133,7 @@ async def stop_server() -> None:
 
         try:
             await asyncio.wait_for(started.wait(), timeout=self.timeout)
-        except Exception:  # pragma: no cover
+        except Exception:  # nocov
             # see if we can await the future for a more helpful error
             await asyncio.wait_for(server_future, timeout=self.timeout)
             raise
@@ -141,25 +142,24 @@ async def stop_server() -> None:
 
     async def __aexit__(
         self,
-        exc_type: Optional[Type[BaseException]],
-        exc_value: Optional[BaseException],
-        traceback: Optional[TracebackType],
+        exc_type: type[BaseException] | None,
+        exc_value: BaseException | None,
+        traceback: TracebackType | None,
     ) -> None:
         await self._exit_stack.aclose()
 
         self.mount(None)  # reset the view
 
         logged_errors = self.list_logged_exceptions(del_log_records=False)
-        if logged_errors:  # pragma: no cover
-            raise LogAssertionError("Unexpected logged exception") from logged_errors[0]
-
-        return None
+        if logged_errors:  # nocov
+            msg = "Unexpected logged exception"
+            raise LogAssertionError(msg) from logged_errors[0]
 
 
 _MountFunc = Callable[["Callable[[], Any] | None"], None]
 
 
-def _hotswap(update_on_change: bool = False) -> Tuple[_MountFunc, ComponentConstructor]:
+def _hotswap(update_on_change: bool = False) -> tuple[_MountFunc, ComponentConstructor]:
     """Swap out components from a layout on the fly.
 
     Since you can't change the component functions used to create a layout
@@ -218,8 +218,6 @@ def swap(constructor: Callable[[], Any] | None) -> None:
             for set_constructor in set_constructor_callbacks:
                 set_constructor(constructor)
 
-            return None
-
     else:
 
         @component
@@ -228,6 +226,5 @@ def HotSwap() -> Any:
 
         def swap(constructor: Callable[[], Any] | None) -> None:
             constructor_ref.current = constructor or (lambda: None)
-            return None
 
     return swap, HotSwap
diff --git a/src/reactpy/testing/common.py b/src/py/reactpy/reactpy/testing/common.py
similarity index 93%
rename from src/reactpy/testing/common.py
rename to src/py/reactpy/reactpy/testing/common.py
index 61b4a9a0e..945c1c31d 100644
--- a/src/reactpy/testing/common.py
+++ b/src/py/reactpy/reactpy/testing/common.py
@@ -4,8 +4,9 @@
 import inspect
 import shutil
 import time
+from collections.abc import Awaitable
 from functools import wraps
-from typing import Any, Awaitable, Callable, Generic, Optional, TypeVar, cast
+from typing import Any, Callable, Generic, TypeVar, cast
 from uuid import uuid4
 from weakref import ref
 
@@ -65,11 +66,9 @@ async def until(
             result = await self._func(*self._args, **self._kwargs)
             if condition(result):
                 break
-            elif (time.time() - started_at) > timeout:  # pragma: no cover
-                raise TimeoutError(
-                    f"Expected {description} after {timeout} "
-                    f"seconds - last value was {result!r}"
-                )
+            elif (time.time() - started_at) > timeout:  # nocov
+                msg = f"Expected {description} after {timeout} seconds - last value was {result!r}"
+                raise TimeoutError(msg)
 
     async def until_is(
         self,
@@ -126,7 +125,7 @@ def MyComponent(thing):
 
     latest: LifeCycleHook
 
-    def __init__(self, index_by_kwarg: Optional[str] = None):
+    def __init__(self, index_by_kwarg: str | None = None):
         self.index_by_kwarg = index_by_kwarg
         self.index: dict[Any, LifeCycleHook] = {}
 
@@ -142,7 +141,8 @@ def capture(self, render_function: Callable[..., Any]) -> Callable[..., Any]:
         @wraps(render_function)
         def wrapper(*args: Any, **kwargs: Any) -> Any:
             self = self_ref()
-            assert self is not None, "Hook catcher has been garbage collected"
+            if self is None:
+                raise RuntimeError("Hook catcher has been garbage collected")
 
             hook = current_hook()
             if self.index_by_kwarg is not None:
diff --git a/src/reactpy/testing/display.py b/src/py/reactpy/reactpy/testing/display.py
similarity index 93%
rename from src/reactpy/testing/display.py
rename to src/py/reactpy/reactpy/testing/display.py
index e97fb5927..bb0d8351d 100644
--- a/src/reactpy/testing/display.py
+++ b/src/py/reactpy/reactpy/testing/display.py
@@ -13,10 +13,9 @@
 )
 
 from reactpy.config import REACTPY_TESTING_DEFAULT_TIMEOUT
+from reactpy.testing.backend import BackendFixture
 from reactpy.types import RootComponentConstructor
 
-from .backend import BackendFixture
-
 
 class DisplayFixture:
     """A fixture for running web-based tests using ``playwright``"""
@@ -57,8 +56,9 @@ async def goto(
 
     async def root_element(self) -> ElementHandle:
         element = await self.page.wait_for_selector("#app", state="attached")
-        if element is None:
-            raise RuntimeError("Root element not attached")  # pragma: no cover
+        if element is None:  # nocov
+            msg = "Root element not attached"
+            raise RuntimeError(msg)
         return element
 
     async def __aenter__(self) -> DisplayFixture:
diff --git a/src/reactpy/testing/logs.py b/src/py/reactpy/reactpy/testing/logs.py
similarity index 98%
rename from src/reactpy/testing/logs.py
rename to src/py/reactpy/reactpy/testing/logs.py
index 0a0bebf7a..e9337b19c 100644
--- a/src/reactpy/testing/logs.py
+++ b/src/py/reactpy/reactpy/testing/logs.py
@@ -2,9 +2,10 @@
 
 import logging
 import re
+from collections.abc import Iterator
 from contextlib import contextmanager
 from traceback import format_exception
-from typing import Any, Iterator, NoReturn
+from typing import Any, NoReturn
 
 from reactpy.logging import ROOT_LOGGER
 
@@ -60,7 +61,7 @@ def assert_reactpy_did_log(
                     )
                 ):
                     break
-            else:  # pragma: no cover
+            else:  # nocov
                 _raise_log_message_error(
                     "Could not find a log record matching the given",
                     match_message,
diff --git a/src/reactpy/types.py b/src/py/reactpy/reactpy/types.py
similarity index 81%
rename from src/reactpy/types.py
rename to src/py/reactpy/reactpy/types.py
index b8ed1ca2a..715b66fff 100644
--- a/src/reactpy/types.py
+++ b/src/py/reactpy/reactpy/types.py
@@ -4,10 +4,10 @@
 - :mod:`reactpy.backend.types`
 """
 
-from .backend.types import BackendImplementation, Connection, Location
-from .core.component import Component
-from .core.hooks import Context
-from .core.types import (
+from reactpy.backend.types import BackendImplementation, Connection, Location
+from reactpy.core.component import Component
+from reactpy.core.hooks import Context
+from reactpy.core.types import (
     ComponentConstructor,
     ComponentType,
     EventHandlerDict,
@@ -26,7 +26,6 @@
     VdomJson,
 )
 
-
 __all__ = [
     "BackendImplementation",
     "Component",
diff --git a/src/reactpy/utils.py b/src/py/reactpy/reactpy/utils.py
similarity index 90%
rename from src/reactpy/utils.py
rename to src/py/reactpy/reactpy/utils.py
index e42097aa0..fead642f2 100644
--- a/src/reactpy/utils.py
+++ b/src/py/reactpy/reactpy/utils.py
@@ -1,8 +1,9 @@
 from __future__ import annotations
 
 import re
+from collections.abc import Iterable
 from itertools import chain
-from typing import Any, Callable, Generic, Iterable, TypeVar, cast
+from typing import Any, Callable, Generic, TypeVar, cast
 
 from lxml import etree
 from lxml.html import fromstring, tostring
@@ -10,7 +11,6 @@
 from reactpy.core.types import VdomDict
 from reactpy.core.vdom import vdom
 
-
 _RefValue = TypeVar("_RefValue")
 _ModelTransform = Callable[[VdomDict], Any]
 _UNDEFINED: Any = object()
@@ -95,8 +95,9 @@ def html_to_vdom(
             If ``True``, raise an exception if the HTML does not perfectly follow HTML5
             syntax.
     """
-    if not isinstance(html, str):  # pragma: no cover
-        raise TypeError(f"Expected html to be a string, not {type(html).__name__}")
+    if not isinstance(html, str):  # nocov
+        msg = f"Expected html to be a string, not {type(html).__name__}"
+        raise TypeError(msg)
 
     # If the user provided a string, convert it to a list of lxml.etree nodes
     try:
@@ -111,14 +112,9 @@ def html_to_vdom(
         )
     except etree.XMLSyntaxError as e:
         if not strict:
-            raise e  # pragma: no cover
-        raise HTMLParseError(
-            "An error has occurred while parsing the HTML.\n\n"
-            "This HTML may be malformatted, or may not perfectly adhere to HTML5.\n"
-            "If you believe the exception above was due to something intentional, "
-            "you can disable the strict parameter on html_to_vdom().\n"
-            "Otherwise, repair your broken HTML and try again."
-        ) from e
+            raise e  # nocov
+        msg = "An error has occurred while parsing the HTML.\n\nThis HTML may be malformatted, or may not perfectly adhere to HTML5.\nIf you believe the exception above was due to something intentional, you can disable the strict parameter on html_to_vdom().\nOtherwise, repair your broken HTML and try again."
+        raise HTMLParseError(msg) from e
 
     return _etree_to_vdom(root_node, transforms)
 
@@ -140,10 +136,9 @@ def _etree_to_vdom(
             dictionary which will be replaced by ``new``. For example, you could use a
             transform function to add highlighting to a ``<code/>`` block.
     """
-    if not isinstance(node, etree._Element):  # pragma: no cover
-        raise TypeError(
-            f"Expected node to be a etree._Element, not {type(node).__name__}"
-        )
+    if not isinstance(node, etree._Element):  # nocov
+        msg = f"Expected node to be a etree._Element, not {type(node).__name__}"
+        raise TypeError(msg)
 
     # Recursively call _etree_to_vdom() on all children
     children = _generate_vdom_children(node, transforms)
@@ -165,7 +160,8 @@ def _add_vdom_to_etree(parent: etree._Element, vdom: VdomDict | dict[str, Any])
     try:
         tag = vdom["tagName"]
     except KeyError as e:
-        raise TypeError(f"Expected a VDOM dict, not {vdom}") from e
+        msg = f"Expected a VDOM dict, not {vdom}"
+        raise TypeError(msg) from e
     else:
         vdom = cast(VdomDict, vdom)
 
@@ -218,7 +214,7 @@ def _mutate_vdom(vdom: VdomDict) -> None:
         and isinstance(vdom["attributes"]["style"], str)
     ):
         # Convince type checker that it's safe to mutate attributes
-        assert isinstance(vdom["attributes"], dict)
+        assert isinstance(vdom["attributes"], dict)  # noqa: S101
 
         # Convert style attribute from str -> dict with camelCase keys
         vdom["attributes"]["style"] = {
@@ -295,9 +291,8 @@ def _vdom_attr_to_html_str(key: str, value: Any) -> tuple[str, str]:
     ):
         key = _CAMEL_CASE_SUB_PATTERN.sub("-", key)
 
-    assert not callable(
-        value
-    ), f"Could not convert callable attribute {key}={value} to HTML"
+    if callable(value):  # nocov
+        raise TypeError(f"Cannot convert callable attribute {key}={value} to HTML")
 
     # Again, we lower the attribute name only to normalize - HTML is case-insensitive:
     # http://w3c.github.io/html-reference/documents.html#case-insensitivity
diff --git a/src/reactpy/web/__init__.py b/src/py/reactpy/reactpy/web/__init__.py
similarity index 87%
rename from src/reactpy/web/__init__.py
rename to src/py/reactpy/reactpy/web/__init__.py
index 6fe239ed9..308429dbb 100644
--- a/src/reactpy/web/__init__.py
+++ b/src/py/reactpy/reactpy/web/__init__.py
@@ -1,4 +1,4 @@
-from .module import (
+from reactpy.web.module import (
     export,
     module_from_file,
     module_from_string,
@@ -6,7 +6,6 @@
     module_from_url,
 )
 
-
 __all__ = [
     "module_from_file",
     "module_from_string",
diff --git a/src/reactpy/web/module.py b/src/py/reactpy/reactpy/web/module.py
similarity index 95%
rename from src/reactpy/web/module.py
rename to src/py/reactpy/reactpy/web/module.py
index efdd2395f..5e615a981 100644
--- a/src/reactpy/web/module.py
+++ b/src/py/reactpy/reactpy/web/module.py
@@ -13,14 +13,12 @@
 from reactpy.config import REACTPY_DEBUG_MODE, REACTPY_WEB_MODULES_DIR
 from reactpy.core.types import ImportSourceDict, VdomDictConstructor
 from reactpy.core.vdom import make_vdom_constructor
-
-from .utils import (
+from reactpy.web.utils import (
     module_name_suffix,
     resolve_module_exports_from_file,
     resolve_module_exports_from_url,
 )
 
-
 logger = logging.getLogger(__name__)
 
 SourceType = NewType("SourceType", str)
@@ -143,7 +141,8 @@ def module_from_template(
 
     template_file = Path(__file__).parent / "templates" / template_file_name
     if not template_file.exists():
-        raise ValueError(f"No template for {template_file_name!r} exists")
+        msg = f"No template for {template_file_name!r} exists"
+        raise ValueError(msg)
 
     variables = {"PACKAGE": package, "CDN": cdn, "VERSION": template_version}
     content = Template(template_file.read_text()).substitute(variables)
@@ -193,7 +192,8 @@ def module_from_file(
     source_file = Path(file).resolve()
     target_file = _web_module_path(name)
     if not source_file.exists():
-        raise FileNotFoundError(f"Source file does not exist: {source_file}")
+        msg = f"Source file does not exist: {source_file}"
+        raise FileNotFoundError(msg)
 
     if not target_file.exists():
         _copy_file(target_file, source_file, symlink)
@@ -351,15 +351,15 @@ def export(
             web_module.export_names is not None
             and export_names not in web_module.export_names
         ):
-            raise ValueError(f"{web_module.source!r} does not export {export_names!r}")
+            msg = f"{web_module.source!r} does not export {export_names!r}"
+            raise ValueError(msg)
         return _make_export(web_module, export_names, fallback, allow_children)
     else:
         if web_module.export_names is not None:
-            missing = list(
-                sorted(set(export_names).difference(web_module.export_names))
-            )
+            missing = sorted(set(export_names).difference(web_module.export_names))
             if missing:
-                raise ValueError(f"{web_module.source!r} does not export {missing!r}")
+                msg = f"{web_module.source!r} does not export {missing!r}"
+                raise ValueError(msg)
         return [
             _make_export(web_module, name, fallback, allow_children)
             for name in export_names
diff --git a/src/reactpy/web/templates/react.js b/src/py/reactpy/reactpy/web/templates/react.js
similarity index 99%
rename from src/reactpy/web/templates/react.js
rename to src/py/reactpy/reactpy/web/templates/react.js
index 00924f85a..5c6a45743 100644
--- a/src/reactpy/web/templates/react.js
+++ b/src/py/reactpy/reactpy/web/templates/react.js
@@ -54,7 +54,7 @@ function makeJsonSafeEventHandler(oldHandler) {
           }
           return true;
         }
-      })
+      }),
     );
   };
 }
diff --git a/src/reactpy/web/utils.py b/src/py/reactpy/reactpy/web/utils.py
similarity index 96%
rename from src/reactpy/web/utils.py
rename to src/py/reactpy/reactpy/web/utils.py
index a1f812187..cf8b8638b 100644
--- a/src/reactpy/web/utils.py
+++ b/src/py/reactpy/reactpy/web/utils.py
@@ -1,12 +1,10 @@
 import logging
 import re
 from pathlib import Path, PurePosixPath
-from typing import Set, Tuple
 from urllib.parse import urlparse
 
 import requests
 
-
 logger = logging.getLogger(__name__)
 
 
@@ -22,7 +20,7 @@ def resolve_module_exports_from_file(
     file: Path,
     max_depth: int,
     is_re_export: bool = False,
-) -> Set[str]:
+) -> set[str]:
     if max_depth == 0:
         logger.warning(f"Did not resolve all exports for {file} - max depth reached")
         return set()
@@ -52,13 +50,13 @@ def resolve_module_exports_from_url(
     url: str,
     max_depth: int,
     is_re_export: bool = False,
-) -> Set[str]:
+) -> set[str]:
     if max_depth == 0:
         logger.warning(f"Did not resolve all exports for {url} - max depth reached")
         return set()
 
     try:
-        text = requests.get(url).text
+        text = requests.get(url, timeout=5).text
     except requests.exceptions.ConnectionError as error:
         reason = "" if error is None else " - {error.errno}"
         logger.warning("Did not resolve exports for url " + url + reason)
@@ -79,9 +77,9 @@ def resolve_module_exports_from_url(
 
 def resolve_module_exports_from_source(
     content: str, exclude_default: bool
-) -> Tuple[Set[str], Set[str]]:
-    names: Set[str] = set()
-    references: Set[str] = set()
+) -> tuple[set[str], set[str]]:
+    names: set[str] = set()
+    references: set[str] = set()
 
     if _JS_DEFAULT_EXPORT_PATTERN.search(content):
         names.add("default")
diff --git a/src/reactpy/widgets.py b/src/py/reactpy/reactpy/widgets.py
similarity index 85%
rename from src/reactpy/widgets.py
rename to src/py/reactpy/reactpy/widgets.py
index 584c3b562..cc19be04d 100644
--- a/src/reactpy/widgets.py
+++ b/src/py/reactpy/reactpy/widgets.py
@@ -1,20 +1,18 @@
 from __future__ import annotations
 
 from base64 import b64encode
-from typing import TYPE_CHECKING, Any, Callable, Sequence, Tuple, TypeVar, Union
-
-from typing_extensions import Protocol
+from collections.abc import Sequence
+from typing import TYPE_CHECKING, Any, Callable, Protocol, TypeVar
 
 import reactpy
-
-from . import html
-from ._warnings import warn
-from .core.types import ComponentConstructor, VdomDict
+from reactpy import html
+from reactpy._warnings import warn
+from reactpy.core.types import ComponentConstructor, VdomDict
 
 
 def image(
     format: str,
-    value: Union[str, bytes] = "",
+    value: str | bytes = "",
     attributes: dict[str, Any] | None = None,
 ) -> VdomDict:
     """Utility for constructing an image from a string or bytes
@@ -22,7 +20,7 @@ def image(
     The source value will automatically be encoded to base64
     """
     if format == "svg":
-        format = "svg+xml"
+        format = "svg+xml"  # noqa: A001
 
     if isinstance(value, str):
         bytes_value = value.encode()
@@ -89,17 +87,17 @@ def __call__(self, value: str) -> _CastTo:
 
 
 if TYPE_CHECKING:
-    from .testing.backend import _MountFunc
+    from reactpy.testing.backend import _MountFunc
 
 
 def hotswap(
     update_on_change: bool = False,
-) -> Tuple[_MountFunc, ComponentConstructor]:  # pragma: no cover
+) -> tuple[_MountFunc, ComponentConstructor]:  # nocov
     warn(
         "The 'hotswap' function is deprecated and will be removed in a future release",
         DeprecationWarning,
         stacklevel=2,
     )
-    from .testing.backend import _hotswap
+    from reactpy.testing.backend import _hotswap
 
     return _hotswap(update_on_change)
diff --git a/tests/__init__.py b/src/py/reactpy/tests/__init__.py
similarity index 100%
rename from tests/__init__.py
rename to src/py/reactpy/tests/__init__.py
diff --git a/tests/conftest.py b/src/py/reactpy/tests/conftest.py
similarity index 97%
rename from tests/conftest.py
rename to src/py/reactpy/tests/conftest.py
index ceadbbb78..1a1f02098 100644
--- a/tests/conftest.py
+++ b/src/py/reactpy/tests/conftest.py
@@ -57,7 +57,7 @@ async def browser(pytestconfig: Config):
 
 @pytest.fixture(scope="session")
 def event_loop():
-    if os.name == "nt":  # pragma: no cover
+    if os.name == "nt":  # nocov
         asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy())
     with open_event_loop() as loop:
         yield loop
diff --git a/tests/test__console/__init__.py b/src/py/reactpy/tests/test__console/__init__.py
similarity index 100%
rename from tests/test__console/__init__.py
rename to src/py/reactpy/tests/test__console/__init__.py
diff --git a/tests/test__console/test_rewrite_camel_case_props.py b/src/py/reactpy/tests/test__console/test_rewrite_camel_case_props.py
similarity index 99%
rename from tests/test__console/test_rewrite_camel_case_props.py
rename to src/py/reactpy/tests/test__console/test_rewrite_camel_case_props.py
index d8746f8c7..47b8baabc 100644
--- a/tests/test__console/test_rewrite_camel_case_props.py
+++ b/src/py/reactpy/tests/test__console/test_rewrite_camel_case_props.py
@@ -10,7 +10,6 @@
     rewrite_camel_case_props,
 )
 
-
 if sys.version_info < (3, 9):
     pytestmark = pytest.mark.skip(reason="ast.unparse is Python>=3.9")
 
diff --git a/tests/test__console/test_rewrite_keys.py b/src/py/reactpy/tests/test__console/test_rewrite_keys.py
similarity index 99%
rename from tests/test__console/test_rewrite_keys.py
rename to src/py/reactpy/tests/test__console/test_rewrite_keys.py
index 6fad036e7..da0b26c4f 100644
--- a/tests/test__console/test_rewrite_keys.py
+++ b/src/py/reactpy/tests/test__console/test_rewrite_keys.py
@@ -7,7 +7,6 @@
 
 from reactpy._console.rewrite_keys import generate_rewrite, rewrite_keys
 
-
 if sys.version_info < (3, 9):
     pytestmark = pytest.mark.skip(reason="ast.unparse is Python>=3.9")
 
diff --git a/tests/test__option.py b/src/py/reactpy/tests/test__option.py
similarity index 98%
rename from tests/test__option.py
rename to src/py/reactpy/tests/test__option.py
index 1fbd6459e..63f2fada8 100644
--- a/tests/test__option.py
+++ b/src/py/reactpy/tests/test__option.py
@@ -105,7 +105,7 @@ def test_deprecated_option():
     opt = DeprecatedOption("is deprecated!", "A_FAKE_OPTION", None)
 
     with pytest.warns(DeprecationWarning, match="is deprecated!"):
-        opt.current
+        assert opt.current is None
 
     with pytest.warns(DeprecationWarning, match="is deprecated!"):
         opt.current = "something"
diff --git a/tests/test_backend/__init__.py b/src/py/reactpy/tests/test_backend/__init__.py
similarity index 100%
rename from tests/test_backend/__init__.py
rename to src/py/reactpy/tests/test_backend/__init__.py
diff --git a/tests/test_backend/test__common.py b/src/py/reactpy/tests/test_backend/test__common.py
similarity index 100%
rename from tests/test_backend/test__common.py
rename to src/py/reactpy/tests/test_backend/test__common.py
diff --git a/tests/test_backend/test_all.py b/src/py/reactpy/tests/test_backend/test_all.py
similarity index 97%
rename from tests/test_backend/test_all.py
rename to src/py/reactpy/tests/test_backend/test_all.py
index fcf6b7286..796f096db 100644
--- a/tests/test_backend/test_all.py
+++ b/src/py/reactpy/tests/test_backend/test_all.py
@@ -1,4 +1,4 @@
-from typing import MutableMapping
+from collections.abc import MutableMapping
 
 import pytest
 
@@ -12,7 +12,7 @@
 
 
 @pytest.fixture(
-    params=list(all_implementations()) + [default_implementation],
+    params=[*list(all_implementations()), default_implementation],
     ids=lambda imp: imp.__name__,
     scope="module",
 )
diff --git a/tests/test_backend/test_utils.py b/src/py/reactpy/tests/test_backend/test_utils.py
similarity index 95%
rename from tests/test_backend/test_utils.py
rename to src/py/reactpy/tests/test_backend/test_utils.py
index 2b36966f5..2a58dc62a 100644
--- a/tests/test_backend/test_utils.py
+++ b/src/py/reactpy/tests/test_backend/test_utils.py
@@ -8,7 +8,7 @@
 from reactpy.backend import flask as flask_implementation
 from reactpy.backend.utils import find_available_port
 from reactpy.backend.utils import run as sync_run
-from reactpy.sample import SampleApp as SampleApp
+from reactpy.sample import SampleApp
 
 
 @pytest.fixture
diff --git a/tests/test_client.py b/src/py/reactpy/tests/test_client.py
similarity index 98%
rename from tests/test_client.py
rename to src/py/reactpy/tests/test_client.py
index ea6a3a672..5a51f8b16 100644
--- a/tests/test_client.py
+++ b/src/py/reactpy/tests/test_client.py
@@ -10,7 +10,6 @@
 from tests.tooling.common import DEFAULT_TYPE_DELAY
 from tests.tooling.hooks import use_counter
 
-
 JS_DIR = Path(__file__).parent / "js"
 
 
@@ -27,7 +26,7 @@ def SomeComponent():
         return reactpy.html._(
             reactpy.html.p({"data_count": count, "id": "count"}, "count", count),
             reactpy.html.button(
-                dict(on_click=lambda e: incr_count(), id="incr"), "incr"
+                {"on_click": lambda e: incr_count(), "id": "incr"}, "incr"
             ),
         )
 
diff --git a/tests/test_config.py b/src/py/reactpy/tests/test_config.py
similarity index 100%
rename from tests/test_config.py
rename to src/py/reactpy/tests/test_config.py
diff --git a/tests/test_core/__init__.py b/src/py/reactpy/tests/test_core/__init__.py
similarity index 100%
rename from tests/test_core/__init__.py
rename to src/py/reactpy/tests/test_core/__init__.py
diff --git a/tests/test_core/test_component.py b/src/py/reactpy/tests/test_core/test_component.py
similarity index 100%
rename from tests/test_core/test_component.py
rename to src/py/reactpy/tests/test_core/test_component.py
diff --git a/tests/test_core/test_events.py b/src/py/reactpy/tests/test_core/test_events.py
similarity index 99%
rename from tests/test_core/test_events.py
rename to src/py/reactpy/tests/test_core/test_events.py
index f6ab2bdc1..67c7c2ba9 100644
--- a/tests/test_core/test_events.py
+++ b/src/py/reactpy/tests/test_core/test_events.py
@@ -193,7 +193,7 @@ def inner_click_no_op(event):
             clicked.current = True
 
         def outer_click_is_not_triggered(event):
-            assert False
+            raise AssertionError()
 
         outer = reactpy.html.div(
             {
diff --git a/tests/test_core/test_hooks.py b/src/py/reactpy/tests/test_core/test_hooks.py
similarity index 97%
rename from tests/test_core/test_hooks.py
rename to src/py/reactpy/tests/test_core/test_hooks.py
index 570319677..ce9afdc12 100644
--- a/tests/test_core/test_hooks.py
+++ b/src/py/reactpy/tests/test_core/test_hooks.py
@@ -557,7 +557,8 @@ async def test_error_in_effect_is_gracefully_handled(caplog):
     def ComponentWithEffect():
         @reactpy.hooks.use_effect
         def bad_effect():
-            raise ValueError("Something went wong :(")
+            msg = "Something went wong :("
+            raise ValueError(msg)
 
         return reactpy.html.div()
 
@@ -566,29 +567,6 @@ def bad_effect():
             await layout.render()  # no error
 
 
-async def test_error_in_effect_cleanup_is_gracefully_handled(caplog):
-    caplog.clear()
-    component_hook = HookCatcher()
-
-    @reactpy.component
-    @component_hook.capture
-    def ComponentWithEffect():
-        @reactpy.hooks.use_effect(dependencies=None)  # force this to run every time
-        def ok_effect():
-            def bad_cleanup():
-                raise ValueError("Something went wong :(")
-
-            return bad_cleanup
-
-        return reactpy.html.div()
-
-    with assert_reactpy_did_log(match_error=r"Layout post-render effect .* failed"):
-        async with reactpy.Layout(ComponentWithEffect()) as layout:
-            await layout.render()
-            component_hook.latest.schedule_render()
-            await layout.render()  # no error
-
-
 async def test_error_in_effect_pre_unmount_cleanup_is_gracefully_handled():
     set_key = reactpy.Ref()
 
@@ -602,7 +580,8 @@ def ComponentWithEffect():
         @reactpy.hooks.use_effect
         def ok_effect():
             def bad_cleanup():
-                raise ValueError("Something went wong :(")
+                msg = "Something went wong :("
+                raise ValueError(msg)
 
             return bad_cleanup
 
@@ -628,7 +607,8 @@ def reducer(count, action):
         elif action == "decrement":
             return count - 1
         else:
-            raise ValueError(f"Unknown action '{action}'")
+            msg = f"Unknown action '{action}'"
+            raise ValueError(msg)
 
     @reactpy.component
     def Counter(initial_count):
@@ -660,7 +640,8 @@ def reducer(count, action):
         if action == "increment":
             return count + 1
         else:
-            raise ValueError(f"Unknown action '{action}'")
+            msg = f"Unknown action '{action}'"
+            raise ValueError(msg)
 
     @reactpy.component
     def ComponentWithUseReduce():
@@ -792,7 +773,7 @@ async def test_use_memo_with_stored_deps_is_empty_tuple_after_deps_are_none():
     def ComponentWithMemo():
         value = reactpy.hooks.use_memo(
             lambda: next(iter_values),
-            deps_used_in_memo.current,  # noqa: ROH202
+            deps_used_in_memo.current,
         )
         used_values.append(value)
         return reactpy.html.div()
@@ -853,7 +834,8 @@ def ComponentWithRef():
 
 def test_bad_schedule_render_callback():
     def bad_callback():
-        raise ValueError("something went wrong")
+        msg = "something went wrong"
+        raise ValueError(msg)
 
     with assert_reactpy_did_log(
         match_message=f"Failed to schedule render via {bad_callback}"
@@ -872,7 +854,7 @@ def CounterWithEffect():
         @reactpy.hooks.use_effect
         def some_effect_that_uses_count():
             """should automatically trigger on count change"""
-            count  # use count in this closure
+            _ = count  # use count in this closure
             did_effect.set()
 
         return reactpy.html.div()
@@ -900,7 +882,7 @@ def CounterWithEffect():
         @reactpy.hooks.use_memo
         def some_memo_func_that_uses_count():
             """should automatically trigger on count change"""
-            count  # use count in this closure
+            _ = count  # use count in this closure
             did_memo.set()
 
         return reactpy.html.div()
@@ -935,11 +917,11 @@ def ComponentUsesContext():
         assert value.current == "something"
 
     @reactpy.component
-    def ComponentUsesContext():
+    def ComponentUsesContext2():
         value.current = reactpy.use_context(Context)
         return html.div()
 
-    async with reactpy.Layout(ComponentUsesContext()) as layout:
+    async with reactpy.Layout(ComponentUsesContext2()) as layout:
         await layout.render()
         assert value.current == "something"
 
@@ -1011,22 +993,21 @@ def Child2():
         await layout.render()
 
 
-async def test_error_in_effect_cleanup_is_gracefully_handled():
+async def test_error_in_layout_effect_cleanup_is_gracefully_handled():
     component_hook = HookCatcher()
 
     @reactpy.component
     @component_hook.capture
     def ComponentWithEffect():
-        hook = current_hook()
-
+        @reactpy.hooks.use_effect(dependencies=None)  # always run
         def bad_effect():
-            raise ValueError("The error message")
+            msg = "The error message"
+            raise ValueError(msg)
 
-        hook.add_effect(COMPONENT_DID_RENDER_EFFECT, bad_effect)
         return reactpy.html.div()
 
     with assert_reactpy_did_log(
-        match_message="Component post-render effect .*? failed",
+        match_message=r"post-render effect .*? failed",
         error_type=ValueError,
         match_error="The error message",
     ):
@@ -1244,7 +1225,6 @@ async def test_use_state_named_tuple():
     @reactpy.component
     def some_component():
         state.current = reactpy.use_state(1)
-        return None
 
     async with reactpy.Layout(some_component()) as layout:
         await layout.render()
@@ -1252,3 +1232,28 @@ def some_component():
         state.current.set_value(2)
         await layout.render()
         assert state.current.value == 2
+
+
+async def test_error_in_component_effect_cleanup_is_gracefully_handled():
+    component_hook = HookCatcher()
+
+    @reactpy.component
+    @component_hook.capture
+    def ComponentWithEffect():
+        hook = current_hook()
+
+        def bad_effect():
+            raise ValueError("The error message")
+
+        hook.add_effect(COMPONENT_DID_RENDER_EFFECT, bad_effect)
+        return reactpy.html.div()
+
+    with assert_reactpy_did_log(
+        match_message="Component post-render effect .*? failed",
+        error_type=ValueError,
+        match_error="The error message",
+    ):
+        async with reactpy.Layout(ComponentWithEffect()) as layout:
+            await layout.render()
+            component_hook.latest.schedule_render()
+            await layout.render()  # no error
diff --git a/tests/test_core/test_layout.py b/src/py/reactpy/tests/test_core/test_layout.py
similarity index 98%
rename from tests/test_core/test_layout.py
rename to src/py/reactpy/tests/test_core/test_layout.py
index d46913314..4aa31fe73 100644
--- a/tests/test_core/test_layout.py
+++ b/src/py/reactpy/tests/test_core/test_layout.py
@@ -58,13 +58,10 @@ def Component():
     component = Component()
     layout = reactpy.Layout(component)
 
-    with pytest.raises(Exception):
+    with pytest.raises(AttributeError):
         await layout.deliver(event_message("something"))
 
-    with pytest.raises(Exception):
-        layout.update(component)
-
-    with pytest.raises(Exception):
+    with pytest.raises(AttributeError):
         await layout.render()
 
 
@@ -171,7 +168,8 @@ def OkChild():
 
     @reactpy.component
     def BadChild():
-        raise ValueError("error from bad child")
+        msg = "error from bad child"
+        raise ValueError(msg)
 
     with assert_reactpy_did_log(match_error="error from bad child"):
         async with reactpy.Layout(Main()) as layout:
@@ -221,7 +219,8 @@ def OkChild():
 
     @reactpy.component
     def BadChild():
-        raise ValueError("error from bad child")
+        msg = "error from bad child"
+        raise ValueError(msg)
 
     with assert_reactpy_did_log(match_error="error from bad child"):
         async with reactpy.Layout(Main()) as layout:
@@ -385,7 +384,7 @@ def wrapper(*args, **kwargs):
     @reactpy.component
     @add_to_live_hooks
     def Outer():
-        nonlocal set_inner_component
+        nonlocal set_inner_component  # noqa PLE0117
         inner_component, set_inner_component = reactpy.hooks.use_state(
             Inner(key="first")
         )
@@ -504,7 +503,8 @@ def good_trigger():
 
         @bad_handler.use
         def bad_trigger():
-            raise ValueError("Called bad trigger")
+            msg = "Called bad trigger"
+            raise ValueError(msg)
 
         children = [
             reactpy.html.button(
@@ -522,7 +522,7 @@ def bad_trigger():
 
     async with reactpy.Layout(MyComponent()) as layout:
         await layout.render()
-        for i in range(3):
+        for _i in range(3):
             event = event_message(good_handler.target)
             await layout.deliver(event)
 
@@ -567,7 +567,8 @@ def callback():
 
             @bad_handler.use
             def callback():
-                raise ValueError("Called bad trigger")
+                msg = "Called bad trigger"
+                raise ValueError(msg)
 
         return reactpy.html.button({"on_click": callback, "id": "good"}, "good")
 
@@ -658,7 +659,7 @@ def HasEventHandlerAtRoot():
     async with reactpy.Layout(HasEventHandlerAtRoot()) as layout:
         await layout.render()
 
-        for i in range(3):
+        for _i in range(3):
             last_event_handler = event_handler.current
             # after this render we should have release the reference to the last handler
             await layout.render()
@@ -680,7 +681,7 @@ def HasNestedEventHandler():
     async with reactpy.Layout(HasNestedEventHandler()) as layout:
         await layout.render()
 
-        for i in range(3):
+        for _i in range(3):
             last_event_handler = event_handler.current
             # after this render we should have release the reference to the last handler
             await layout.render()
@@ -753,7 +754,8 @@ async def test_log_error_on_bad_event_handler():
     def ComponentWithBadEventHandler():
         @bad_handler.use
         def raise_error():
-            raise Exception("bad event handler")
+            msg = "bad event handler"
+            raise Exception(msg)
 
         return reactpy.html.button({"on_click": raise_error})
 
@@ -777,7 +779,6 @@ def Parent():
     @reactpy.component
     @child_hook.capture
     def Child(state):
-        reactpy.hooks.use_effect(lambda: lambda: print("unmount", state))
         return reactpy.html.div(state)
 
     with assert_reactpy_did_log(
@@ -893,7 +894,7 @@ def HasState():
     async with reactpy.Layout(Root()) as layout:
         await layout.render()
 
-        for i in range(5):
+        for _i in range(5):
             last_state = state.current
             root_hook.latest.schedule_render()
             await layout.render()
@@ -1027,7 +1028,7 @@ async def record_if_state_is_reset():
         assert effect_calls_without_state == {"some-key", "key-0"}
         did_call_effect.clear()
 
-        for i in range(1, 5):
+        for _i in range(1, 5):
             await layout.deliver(event_message(set_child_key_num.target))
             await layout.render()
             assert effect_calls_without_state == {"some-key", "key-0"}
@@ -1167,7 +1168,6 @@ def Parent():
     def Child():
         nonlocal schedule_removed_child_render
         schedule_removed_child_render = use_force_render()
-        return None
 
     async with reactpy.Layout(Parent()) as layout:
         await layout.render()
diff --git a/tests/test_core/test_serve.py b/src/py/reactpy/tests/test_core/test_serve.py
similarity index 97%
rename from tests/test_core/test_serve.py
rename to src/py/reactpy/tests/test_core/test_serve.py
index b875b3649..4d39f3e3d 100644
--- a/tests/test_core/test_serve.py
+++ b/src/py/reactpy/tests/test_core/test_serve.py
@@ -1,5 +1,6 @@
 import asyncio
-from typing import Any, Sequence
+from collections.abc import Sequence
+from typing import Any
 
 from jsonpointer import set_pointer
 
@@ -10,7 +11,6 @@
 from reactpy.testing import StaticEventHandler
 from tests.tooling.common import event_message
 
-
 EVENT_NAME = "on_event"
 STATIC_EVENT_HANDLER = StaticEventHandler()
 
@@ -122,7 +122,7 @@ async def handle_event():
     send_queue = asyncio.Queue()
     recv_queue = asyncio.Queue()
 
-    asyncio.ensure_future(
+    task = asyncio.create_task(
         serve_layout(
             reactpy.Layout(ComponentWithTwoEventHandlers()),
             send_queue.put,
@@ -135,3 +135,5 @@ async def handle_event():
 
     await recv_queue.put(event_message(non_blocked_handler.target))
     await second_event_did_execute.wait()
+
+    task.cancel()
diff --git a/tests/test_core/test_vdom.py b/src/py/reactpy/tests/test_core/test_vdom.py
similarity index 99%
rename from tests/test_core/test_vdom.py
rename to src/py/reactpy/tests/test_core/test_vdom.py
index 1b86207de..2aac75bb4 100644
--- a/tests/test_core/test_vdom.py
+++ b/src/py/reactpy/tests/test_core/test_vdom.py
@@ -9,7 +9,6 @@
 from reactpy.core.types import VdomDict
 from reactpy.core.vdom import is_vdom, make_vdom_constructor, validate_vdom_json
 
-
 FAKE_EVENT_HANDLER = EventHandler(lambda data: None)
 FAKE_EVENT_HANDLER_DICT = {"on_event": FAKE_EVENT_HANDLER}
 
@@ -70,7 +69,7 @@ def test_is_vdom(result, value):
             {"tagName": "div", "children": [0, 1, 2]},
         ),
         (
-            reactpy.vdom("div", map(lambda x: x**2, [1, 2, 3])),
+            reactpy.vdom("div", (x**2 for x in [1, 2, 3])),
             {"tagName": "div", "children": [1, 4, 9]},
         ),
     ],
diff --git a/tests/test_html.py b/src/py/reactpy/tests/test_html.py
similarity index 100%
rename from tests/test_html.py
rename to src/py/reactpy/tests/test_html.py
diff --git a/tests/test_sample.py b/src/py/reactpy/tests/test_sample.py
similarity index 100%
rename from tests/test_sample.py
rename to src/py/reactpy/tests/test_sample.py
diff --git a/tests/test_testing.py b/src/py/reactpy/tests/test_testing.py
similarity index 91%
rename from tests/test_testing.py
rename to src/py/reactpy/tests/test_testing.py
index 5651f93bc..760e68af3 100644
--- a/tests/test_testing.py
+++ b/src/py/reactpy/tests/test_testing.py
@@ -6,7 +6,7 @@
 from reactpy import Ref, component, html, testing
 from reactpy.backend import starlette as starlette_implementation
 from reactpy.logging import ROOT_LOGGER
-from reactpy.sample import SampleApp as SampleApp
+from reactpy.sample import SampleApp
 from reactpy.testing.backend import _hotswap
 from reactpy.testing.display import DisplayFixture
 
@@ -14,7 +14,8 @@
 def test_assert_reactpy_logged_does_not_supress_errors():
     with pytest.raises(RuntimeError, match="expected error"):
         with testing.assert_reactpy_did_log():
-            raise RuntimeError("expected error")
+            msg = "expected error"
+            raise RuntimeError(msg)
 
 
 def test_assert_reactpy_logged_message():
@@ -32,7 +33,8 @@ def test_assert_reactpy_logged_error():
         match_error="my value error",
     ):
         try:
-            raise ValueError("my value error")
+            msg = "my value error"
+            raise ValueError(msg)
         except ValueError:
             ROOT_LOGGER.exception("log message")
 
@@ -47,7 +49,8 @@ def test_assert_reactpy_logged_error():
         ):
             try:
                 # change error type
-                raise RuntimeError("my value error")
+                msg = "my value error"
+                raise RuntimeError(msg)
             except RuntimeError:
                 ROOT_LOGGER.exception("log message")
 
@@ -62,7 +65,8 @@ def test_assert_reactpy_logged_error():
         ):
             try:
                 # change error message
-                raise ValueError("something else")
+                msg = "something else"
+                raise ValueError(msg)
             except ValueError:
                 ROOT_LOGGER.exception("log message")
 
@@ -77,7 +81,8 @@ def test_assert_reactpy_logged_error():
         ):
             try:
                 # change error message
-                raise ValueError("my error message")
+                msg = "my error message"
+                raise ValueError(msg)
             except ValueError:
                 ROOT_LOGGER.exception("something else")
 
@@ -125,7 +130,8 @@ def test_assert_reactpy_did_not_log():
             match_error=r".*",
         ):
             try:
-                raise Exception("something")
+                msg = "something"
+                raise Exception(msg)
             except Exception:
                 ROOT_LOGGER.exception("something")
 
@@ -157,7 +163,8 @@ def test_list_logged_excptions():
         ROOT_LOGGER.info("A non-error log message")
 
         try:
-            raise ValueError("An error for testing")
+            msg = "An error for testing"
+            raise ValueError(msg)
         except Exception as error:
             ROOT_LOGGER.exception("Log the error")
             the_error = error
diff --git a/tests/test_utils.py b/src/py/reactpy/tests/test_utils.py
similarity index 99%
rename from tests/test_utils.py
rename to src/py/reactpy/tests/test_utils.py
index d3bc64e04..c764b04af 100644
--- a/tests/test_utils.py
+++ b/src/py/reactpy/tests/test_utils.py
@@ -24,7 +24,7 @@ def test_basic_ref_behavior():
 
     r = reactpy.Ref()
     with pytest.raises(AttributeError):
-        r.current
+        r.current  # noqa B018
 
     r.current = 4
     assert r.current == 4
diff --git a/tests/test_web/__init__.py b/src/py/reactpy/tests/test_web/__init__.py
similarity index 100%
rename from tests/test_web/__init__.py
rename to src/py/reactpy/tests/test_web/__init__.py
diff --git a/tests/test_web/js_fixtures/component-can-have-child.js b/src/py/reactpy/tests/test_web/js_fixtures/component-can-have-child.js
similarity index 90%
rename from tests/test_web/js_fixtures/component-can-have-child.js
rename to src/py/reactpy/tests/test_web/js_fixtures/component-can-have-child.js
index e1e892ca0..fd443b164 100644
--- a/tests/test_web/js_fixtures/component-can-have-child.js
+++ b/src/py/reactpy/tests/test_web/js_fixtures/component-can-have-child.js
@@ -8,7 +8,7 @@ export function bind(node, config) {
     create: (type, props, children) => h(type, props, ...children),
     render: (element) => render(element, node),
     unmount: () => render(null, node),
-  }
+  };
 }
 
 // The intention here is that Child components are passed in here so we check that the
@@ -19,9 +19,9 @@ export function Parent(props) {
       <p>the parent</p>
       <ul id="the-parent">${props.children}</div>
     </div>
-  `
+  `;
 }
 
 export function Child({ index }) {
-  return html`<li id="child-${index}">child ${index}</li>`
+  return html`<li id="child-${index}">child ${index}</li>`;
 }
diff --git a/src/py/reactpy/tests/test_web/js_fixtures/export-resolution/index.js b/src/py/reactpy/tests/test_web/js_fixtures/export-resolution/index.js
new file mode 100644
index 000000000..372f108bd
--- /dev/null
+++ b/src/py/reactpy/tests/test_web/js_fixtures/export-resolution/index.js
@@ -0,0 +1,2 @@
+export { index as Index };
+export * from "./one.js";
diff --git a/tests/test_web/js_fixtures/export-resolution/one.js b/src/py/reactpy/tests/test_web/js_fixtures/export-resolution/one.js
similarity index 88%
rename from tests/test_web/js_fixtures/export-resolution/one.js
rename to src/py/reactpy/tests/test_web/js_fixtures/export-resolution/one.js
index 1d404b1cc..ddea9d2cb 100644
--- a/tests/test_web/js_fixtures/export-resolution/one.js
+++ b/src/py/reactpy/tests/test_web/js_fixtures/export-resolution/one.js
@@ -1,4 +1,4 @@
-export {one as One};
+export { one as One };
 // use ../ just to check that it works
 export * from "../export-resolution/two.js";
 // this default should not be exported by the * re-export in index.js
diff --git a/tests/test_web/js_fixtures/export-resolution/two.js b/src/py/reactpy/tests/test_web/js_fixtures/export-resolution/two.js
similarity index 65%
rename from tests/test_web/js_fixtures/export-resolution/two.js
rename to src/py/reactpy/tests/test_web/js_fixtures/export-resolution/two.js
index 4e1d807c2..f01389d2e 100644
--- a/tests/test_web/js_fixtures/export-resolution/two.js
+++ b/src/py/reactpy/tests/test_web/js_fixtures/export-resolution/two.js
@@ -1,2 +1,2 @@
-export {two as Two};
+export { two as Two };
 export * from "https://some.external.url";
diff --git a/tests/test_web/js_fixtures/exports-syntax.js b/src/py/reactpy/tests/test_web/js_fixtures/exports-syntax.js
similarity index 100%
rename from tests/test_web/js_fixtures/exports-syntax.js
rename to src/py/reactpy/tests/test_web/js_fixtures/exports-syntax.js
diff --git a/tests/test_web/js_fixtures/exports-two-components.js b/src/py/reactpy/tests/test_web/js_fixtures/exports-two-components.js
similarity index 80%
rename from tests/test_web/js_fixtures/exports-two-components.js
rename to src/py/reactpy/tests/test_web/js_fixtures/exports-two-components.js
index e647ac928..10aa7fdbe 100644
--- a/tests/test_web/js_fixtures/exports-two-components.js
+++ b/src/py/reactpy/tests/test_web/js_fixtures/exports-two-components.js
@@ -8,13 +8,13 @@ export function bind(node, config) {
     create: (type, props, children) => h(type, props, ...children),
     render: (element) => render(element, node),
     unmount: () => render(null, node),
-  }
+  };
 }
 
 export function Header1(props) {
-  return h("h1", {id: props.id}, props.text);
+  return h("h1", { id: props.id }, props.text);
 }
 
 export function Header2(props) {
-  return h("h2", {id: props.id}, props.text);
+  return h("h2", { id: props.id }, props.text);
 }
diff --git a/tests/test_web/js_fixtures/set-flag-when-unmount-is-called.js b/src/py/reactpy/tests/test_web/js_fixtures/set-flag-when-unmount-is-called.js
similarity index 99%
rename from tests/test_web/js_fixtures/set-flag-when-unmount-is-called.js
rename to src/py/reactpy/tests/test_web/js_fixtures/set-flag-when-unmount-is-called.js
index a58d89e9f..dbb1a1c99 100644
--- a/tests/test_web/js_fixtures/set-flag-when-unmount-is-called.js
+++ b/src/py/reactpy/tests/test_web/js_fixtures/set-flag-when-unmount-is-called.js
@@ -3,7 +3,7 @@ export function bind(node, config) {
     create: (type, props, children) => type(props),
     render: (element) => renderElement(element, node),
     unmount: () => unmountElement(node),
-  }
+  };
 }
 
 export function renderElement(element, container) {
diff --git a/tests/test_web/js_fixtures/simple-button.js b/src/py/reactpy/tests/test_web/js_fixtures/simple-button.js
similarity index 95%
rename from tests/test_web/js_fixtures/simple-button.js
rename to src/py/reactpy/tests/test_web/js_fixtures/simple-button.js
index 00b094138..2b49f505b 100644
--- a/tests/test_web/js_fixtures/simple-button.js
+++ b/src/py/reactpy/tests/test_web/js_fixtures/simple-button.js
@@ -8,7 +8,7 @@ export function bind(node, config) {
     create: (type, props, children) => h(type, props, ...children),
     render: (element) => render(element, node),
     unmount: () => render(null, node),
-  }
+  };
 }
 
 export function SimpleButton(props) {
@@ -20,6 +20,6 @@ export function SimpleButton(props) {
         props.onClick({ data: props.eventResponseData });
       },
     },
-    "simple button"
+    "simple button",
   );
 }
diff --git a/tests/test_web/test_module.py b/src/py/reactpy/tests/test_web/test_module.py
similarity index 99%
rename from tests/test_web/test_module.py
rename to src/py/reactpy/tests/test_web/test_module.py
index 4b0de2af1..f8783337d 100644
--- a/tests/test_web/test_module.py
+++ b/src/py/reactpy/tests/test_web/test_module.py
@@ -14,7 +14,6 @@
 )
 from reactpy.web.module import NAME_SOURCE, WebModule
 
-
 JS_FIXTURES_DIR = Path(__file__).parent / "js_fixtures"
 
 
diff --git a/tests/test_web/test_utils.py b/src/py/reactpy/tests/test_web/test_utils.py
similarity index 99%
rename from tests/test_web/test_utils.py
rename to src/py/reactpy/tests/test_web/test_utils.py
index 4ed01dd83..14c3e2e13 100644
--- a/tests/test_web/test_utils.py
+++ b/src/py/reactpy/tests/test_web/test_utils.py
@@ -11,7 +11,6 @@
     resolve_module_exports_from_url,
 )
 
-
 JS_FIXTURES_DIR = Path(__file__).parent / "js_fixtures"
 
 
diff --git a/tests/test_widgets.py b/src/py/reactpy/tests/test_widgets.py
similarity index 99%
rename from tests/test_widgets.py
rename to src/py/reactpy/tests/test_widgets.py
index e4ac1a341..d786fded0 100644
--- a/tests/test_widgets.py
+++ b/src/py/reactpy/tests/test_widgets.py
@@ -5,7 +5,6 @@
 from reactpy.testing import DisplayFixture, poll
 from tests.tooling.common import DEFAULT_TYPE_DELAY
 
-
 HERE = Path(__file__).parent
 
 
diff --git a/tests/tooling/__init__.py b/src/py/reactpy/tests/tooling/__init__.py
similarity index 100%
rename from tests/tooling/__init__.py
rename to src/py/reactpy/tests/tooling/__init__.py
diff --git a/tests/tooling/asserts.py b/src/py/reactpy/tests/tooling/asserts.py
similarity index 62%
rename from tests/tooling/asserts.py
rename to src/py/reactpy/tests/tooling/asserts.py
index 49105fb80..ac84aa0ba 100644
--- a/tests/tooling/asserts.py
+++ b/src/py/reactpy/tests/tooling/asserts.py
@@ -1,5 +1,5 @@
 def assert_same_items(left, right):
     """Check that two unordered sequences are equal (only works if reprs are equal)"""
-    sorted_left = list(sorted(left, key=repr))
-    sorted_right = list(sorted(right, key=repr))
+    sorted_left = sorted(left, key=repr)
+    sorted_right = sorted(right, key=repr)
     assert sorted_left == sorted_right
diff --git a/tests/tooling/common.py b/src/py/reactpy/tests/tooling/common.py
similarity index 99%
rename from tests/tooling/common.py
rename to src/py/reactpy/tests/tooling/common.py
index 8a929edce..b84a3ac96 100644
--- a/tests/tooling/common.py
+++ b/src/py/reactpy/tests/tooling/common.py
@@ -2,7 +2,6 @@
 
 from reactpy.core.types import LayoutEventMessage, LayoutUpdateMessage
 
-
 # see: https://github.com/microsoft/playwright-python/issues/1614
 DEFAULT_TYPE_DELAY = 100  # miliseconds
 
diff --git a/tests/tooling/hooks.py b/src/py/reactpy/tests/tooling/hooks.py
similarity index 100%
rename from tests/tooling/hooks.py
rename to src/py/reactpy/tests/tooling/hooks.py
diff --git a/tests/tooling/loop.py b/src/py/reactpy/tests/tooling/loop.py
similarity index 75%
rename from tests/tooling/loop.py
rename to src/py/reactpy/tests/tooling/loop.py
index db6f91b83..f9e100981 100644
--- a/tests/tooling/loop.py
+++ b/src/py/reactpy/tests/tooling/loop.py
@@ -1,10 +1,9 @@
 import asyncio
-import sys
 import threading
 import time
 from asyncio import wait_for
+from collections.abc import Iterator
 from contextlib import contextmanager
-from typing import Iterator
 
 from reactpy.config import REACTPY_TESTING_DEFAULT_TIMEOUT
 
@@ -32,24 +31,20 @@ def open_event_loop(as_current: bool = True) -> Iterator[asyncio.AbstractEventLo
                         REACTPY_TESTING_DEFAULT_TIMEOUT.current,
                     )
                 )
-                if sys.version_info >= (3, 9):
-                    # shutdown_default_executor only available in Python 3.9+
-                    loop.run_until_complete(
-                        wait_for(
-                            loop.shutdown_default_executor(),
-                            REACTPY_TESTING_DEFAULT_TIMEOUT.current,
-                        )
+                loop.run_until_complete(
+                    wait_for(
+                        loop.shutdown_default_executor(),
+                        REACTPY_TESTING_DEFAULT_TIMEOUT.current,
                     )
+                )
         finally:
             if as_current:
                 asyncio.set_event_loop(None)
             start = time.time()
             while loop.is_running():
                 if (time.time() - start) > REACTPY_TESTING_DEFAULT_TIMEOUT.current:
-                    raise TimeoutError(
-                        "Failed to stop loop after "
-                        f"{REACTPY_TESTING_DEFAULT_TIMEOUT.current} seconds"
-                    )
+                    msg = f"Failed to stop loop after {REACTPY_TESTING_DEFAULT_TIMEOUT.current} seconds"
+                    raise TimeoutError(msg)
                 time.sleep(0.1)
             loop.close()
 
@@ -79,10 +74,9 @@ def one_task_finished(future):
                 REACTPY_TESTING_DEFAULT_TIMEOUT.current,
             )
         )
-    else:
-        # user was responsible for cancelling all tasks
-        if not done.wait(timeout=3):
-            raise TimeoutError("Could not stop event loop in time")
+    elif not done.wait(timeout=3):  # user was responsible for cancelling all tasks
+        msg = "Could not stop event loop in time"
+        raise TimeoutError(msg)
 
     for task in to_cancel:
         if task.cancelled():
diff --git a/tasks.py b/tasks.py
new file mode 100644
index 000000000..0a0097c78
--- /dev/null
+++ b/tasks.py
@@ -0,0 +1,426 @@
+from __future__ import annotations
+
+import json
+import logging
+import os
+import re
+import sys
+from dataclasses import dataclass
+from pathlib import Path
+from shutil import rmtree
+from typing import TYPE_CHECKING, Any, Callable
+
+import semver
+import toml
+from invoke import task
+from invoke.context import Context
+from invoke.exceptions import Exit
+
+# --- Typing Preamble ------------------------------------------------------------------
+
+
+if TYPE_CHECKING:
+    # not available in typing module until Python 3.8
+    # not available in typing module until Python 3.10
+    from typing import Literal, Protocol, TypeAlias
+
+    class ReleasePrepFunc(Protocol):
+        def __call__(
+            self, context: Context, package: PackageInfo
+        ) -> Callable[[bool], None]:
+            ...
+
+    LanguageName: TypeAlias = "Literal['py', 'js']"
+
+
+# --- Constants ------------------------------------------------------------------------
+
+
+log = logging.getLogger(__name__)
+log.setLevel("INFO")
+log_handler = logging.StreamHandler(sys.stdout)
+log_handler.setFormatter(logging.Formatter("%(message)s"))
+log.addHandler(log_handler)
+
+
+# --- Constants ------------------------------------------------------------------------
+
+
+ROOT = Path(__file__).parent
+DOCS_DIR = ROOT / "docs"
+SRC_DIR = ROOT / "src"
+JS_DIR = SRC_DIR / "js"
+PY_DIR = SRC_DIR / "py"
+PY_PROJECTS = [p for p in PY_DIR.iterdir() if (p / "pyproject.toml").exists()]
+TAG_PATTERN = re.compile(
+    # start
+    r"^"
+    # package name
+    r"(?P<name>[0-9a-zA-Z-@/]+)-"
+    # package version
+    r"v(?P<version>[0-9][0-9a-zA-Z-\.\+]*)"
+    # end
+    r"$"
+)
+
+
+# --- Tasks ----------------------------------------------------------------------------
+
+
+@task
+def env(context: Context):
+    """Install development environment"""
+    env_py(context)
+    env_js(context)
+
+
+@task
+def env_py(context: Context):
+    """Install Python development environment"""
+    for py_proj in PY_PROJECTS:
+        py_proj_toml = toml.load(py_proj / "pyproject.toml")
+        hatch_default_env = py_proj_toml["tool"]["hatch"]["envs"].get("default", {})
+        hatch_default_features = hatch_default_env.get("features", [])
+        hatch_default_deps = hatch_default_env.get("dependencies", [])
+        with context.cd(py_proj):
+            context.run(f"pip install '.[{','.join(hatch_default_features)}]'")
+        context.run(f"pip install {' '.join(map(repr, hatch_default_deps))}")
+
+
+@task
+def env_js(context: Context):
+    """Install JS development environment"""
+    in_js(
+        context,
+        "npm ci",
+        "npm run build",
+        hide="out",
+    )
+
+
+@task
+def lint_py(context: Context, fix: bool = False):
+    """Run linters and type checkers"""
+    if fix:
+        context.run("ruff --fix .")
+    else:
+        context.run("ruff .")
+        context.run("black --check --diff .")
+        in_py(
+            context,
+            f"flake8 --toml-config {ROOT / 'pyproject.toml'} .",
+            "hatch run lint:all",
+        )
+
+
+@task(pre=[env_js])
+def lint_js(context: Context, fix: bool = False):
+    """Run linters and type checkers"""
+    if fix:
+        in_js(context, "npm run fix:format")
+    else:
+        in_js(context, "npm run check:format")
+    in_js(context, "npm run check:types")
+
+
+@task
+def test_py(context: Context, no_cov: bool = False):
+    """Run test suites"""
+    in_py(
+        context,
+        f"hatch run {'test' if no_cov else 'cov'} --maxfail=3 --reruns=3",
+    )
+
+
+@task(pre=[env_js])
+def test_js(context: Context):
+    """Run test suites"""
+    in_js(context, "npm run check:tests")
+
+
+@task(pre=[env_py])
+def test_docs(context: Context):
+    with context.cd(DOCS_DIR):
+        context.run("poetry install")
+        context.run(
+            "poetry run sphinx-build "
+            "-a "  # re-write all output files
+            "-T "  # show full tracebacks
+            "-W "  # turn warnings into errors
+            "--keep-going "  # complete the build, but still report warnings as errors
+            "-b doctest "
+            "source "
+            "build",
+        )
+        context.run("poetry run sphinx-build -b doctest source build")
+
+    context.run("docker build . --file ./docs/Dockerfile")
+
+
+@task
+def docs(context: Context, docker: bool = False):
+    """Build documentation"""
+    if docker:
+        _docker_docs(context)
+    else:
+        _live_docs(context)
+
+
+def _docker_docs(context: Context) -> None:
+    context.run("docker build . --file ./docs/Dockerfile --tag reactpy-docs:latest")
+    context.run(
+        "docker run -it -p 5000:5000 -e DEBUG=1 --rm reactpy-docs:latest", pty=True
+    )
+
+
+def _live_docs(context: Context) -> None:
+    with context.cd(DOCS_DIR):
+        context.run("poetry install")
+        context.run(
+            "poetry run python main.py "
+            "--open-browser "
+            # watch python source too
+            "--watch=../src/py "
+            # for some reason this matches absolute paths
+            "--ignore=**/_auto/* "
+            "--ignore=**/_static/custom.js "
+            "--ignore=**/node_modules/* "
+            "--ignore=**/package-lock.json "
+            "-a "
+            "-E "
+            "-b "
+            "html "
+            "source "
+            "build"
+        )
+
+
+@task
+def publish(context: Context, dry_run: str = ""):
+    """Publish packages that have been tagged for release in the current commit
+
+    To perform a test run use `--dry-run=<name>-v<version>` to specify a comma-separated
+    list of tags to simulate a release of. For example, to simulate a release of
+    `@foo/bar-v1.2.3` and `baz-v4.5.6` use `--dry-run=@foo/bar-v1.2.3,baz-v4.5.6`.
+    """
+    packages = get_packages(context)
+
+    release_prep: dict[LanguageName, ReleasePrepFunc] = {
+        "js": prepare_js_release,
+        "py": prepare_py_release,
+    }
+
+    parsed_tags: list[TagInfo] = [
+        parse_tag(tag) for tag in dry_run.split(",") or get_current_tags(context)
+    ]
+
+    publishers: list[Callable[[bool], None]] = []
+    for tag_info in parsed_tags:
+        if tag_info.name not in packages:
+            msg = f"Tag {tag_info.tag} references package {tag_info.name} that does not exist"
+            raise Exit(msg)
+
+        pkg_info = packages[tag_info.name]
+        if pkg_info.version != tag_info.version:
+            msg = f"Tag {tag_info.tag} references version {tag_info.version} of package {tag_info.name}, but the current version is {pkg_info.version}"
+            raise Exit(msg)
+
+        log.info(f"Preparing {tag_info.name} for release...")
+        publishers.append(release_prep[pkg_info.language](context, pkg_info))
+
+    for publish in publishers:
+        publish(bool(dry_run))
+
+
+# --- Utilities ------------------------------------------------------------------------
+
+
+def in_py(context: Context, *commands: str, **kwargs: Any) -> None:
+    for p in PY_PROJECTS:
+        with context.cd(p):
+            log.info(f"Running commands in {p}...")
+            for c in commands:
+                context.run(c, **kwargs)
+
+
+def in_js(context: Context, *commands: str, **kwargs: Any) -> None:
+    with context.cd(JS_DIR):
+        for c in commands:
+            context.run(c, **kwargs)
+
+
+def get_packages(context: Context) -> dict[str, PackageInfo]:
+    packages: list[PackageInfo] = []
+
+    for maybe_pkg in PY_DIR.glob("*"):
+        if (maybe_pkg / "pyproject.toml").exists():
+            packages.append(make_py_pkg_info(context, maybe_pkg))
+        else:
+            msg = f"unexpected dir or file: {maybe_pkg}"
+            raise Exit(msg)
+
+    packages_dir = JS_DIR / "packages"
+    for maybe_pkg in packages_dir.glob("*"):
+        if (maybe_pkg / "package.json").exists():
+            packages.append(make_js_pkg_info(maybe_pkg))
+        elif maybe_pkg.is_dir():
+            for maybe_ns_pkg in maybe_pkg.glob("*"):
+                if (maybe_ns_pkg / "package.json").exists():
+                    packages.append(make_js_pkg_info(maybe_ns_pkg))
+        else:
+            msg = f"unexpected dir or file: {maybe_pkg}"
+            raise Exit(msg)
+
+    packages_by_name = {p.name: p for p in packages}
+    if len(packages_by_name) != len(packages):
+        raise Exit("duplicate package names detected")
+
+    return packages_by_name
+
+
+def make_py_pkg_info(context: Context, pkg_dir: Path) -> PackageInfo:
+    with context.cd(pkg_dir):
+        proj_metadata = json.loads(context.run("hatch project metadata").stdout)
+    return PackageInfo(
+        name=proj_metadata["name"],
+        path=pkg_dir,
+        language="py",
+        version=proj_metadata["version"],
+    )
+
+
+def make_js_pkg_info(pkg_dir: Path) -> PackageInfo:
+    with (pkg_dir / "package.json").open() as f:
+        pkg_json = json.load(f)
+    return PackageInfo(
+        name=pkg_json["name"],
+        path=pkg_dir,
+        language="js",
+        version=pkg_json["version"],
+    )
+
+
+@dataclass
+class PackageInfo:
+    name: str
+    path: Path
+    language: LanguageName
+    version: str
+
+
+def get_current_tags(context: Context) -> set[str]:
+    """Get tags for the current commit"""
+    # check if unstaged changes
+    try:
+        context.run("git diff --cached --exit-code", hide=True)
+        context.run("git diff --exit-code", hide=True)
+    except Exception:
+        log.error("Cannot create a tag - there are uncommited changes")
+        return set()
+
+    tags_per_commit: dict[str, list[str]] = {}
+    for commit, tag in map(
+        str.split,
+        context.run(
+            r"git for-each-ref --format '%(objectname) %(refname:short)' refs/tags",
+            hide=True,
+        ).stdout.splitlines(),
+    ):
+        tags_per_commit.setdefault(commit, []).append(tag)
+
+    current_commit = context.run(
+        "git rev-parse HEAD", silent=True, external=True
+    ).stdout.strip()
+    tags = set(tags_per_commit.get(current_commit, set()))
+
+    if not tags:
+        log.error("No tags found for current commit")
+
+    for t in tags:
+        if not TAG_PATTERN.match(t):
+            msg = f"Invalid tag: {t}"
+            raise Exit(msg)
+
+    log.info(f"Found tags: {tags}")
+
+    return tags
+
+
+def parse_tag(tag: str) -> TagInfo:
+    match = TAG_PATTERN.match(tag)
+    if not match:
+        msg = f"Invalid tag: {tag}"
+        raise Exit(msg)
+
+    version = match.group("version")
+    if not semver.Version.is_valid(version):
+        raise Exit(f"Invalid version: {version} in tag {tag}")
+
+    return TagInfo(tag=tag, name=match.group("name"), version=match.group("version"))
+
+
+@dataclass
+class TagInfo:
+    tag: str
+    name: str
+    version: str
+
+
+def prepare_js_release(
+    context: Context, package: PackageInfo
+) -> Callable[[bool], None]:
+    node_auth_token = os.getenv("NODE_AUTH_TOKEN")
+    if node_auth_token is None:
+        msg = "NODE_AUTH_TOKEN environment variable must be set"
+        raise Exit(msg)
+
+    with context.cd(JS_DIR):
+        context.run("npm ci")
+        context.run("npm run build")
+
+    def publish(dry_run: bool) -> None:
+        with context.cd(JS_DIR):
+            if dry_run:
+                context.run(f"npm --workspace {package.name} pack --dry-run")
+                return
+            context.run(
+                f"npm --workspace {package.name} publish --access public",
+                env={"NODE_AUTH_TOKEN": node_auth_token},
+            )
+
+    return publish
+
+
+def prepare_py_release(
+    context: Context, package: PackageInfo
+) -> Callable[[bool], None]:
+    twine_username = os.getenv("PYPI_USERNAME")
+    twine_password = os.getenv("PYPI_PASSWORD")
+
+    if not (twine_password and twine_username):
+        msg = "PYPI_USERNAME and PYPI_PASSWORD environment variables must be set"
+        raise Exit(msg)
+
+    for build_dir_name in ["build", "dist"]:
+        build_dir_path = Path.cwd() / build_dir_name
+        if build_dir_path.exists():
+            rmtree(str(build_dir_path))
+
+    with context.cd(package.path):
+        context.run("hatch build")
+
+    def publish(dry_run: bool):
+        with context.cd(package.path):
+            if dry_run:
+                context.run("twine check dist/*")
+                return
+
+            context.run(
+                "twine upload dist/*",
+                env_dict={
+                    "TWINE_USERNAME": twine_username,
+                    "TWINE_PASSWORD": twine_password,
+                },
+            )
+
+    return publish
diff --git a/tests/test_web/js_fixtures/export-resolution/index.js b/tests/test_web/js_fixtures/export-resolution/index.js
deleted file mode 100644
index 2f1f46a51..000000000
--- a/tests/test_web/js_fixtures/export-resolution/index.js
+++ /dev/null
@@ -1,2 +0,0 @@
-export {index as Index};
-export * from "./one.js";

From e950966b974a5dae298cfad04a71a370a0985c93 Mon Sep 17 00:00:00 2001
From: Ryan Morshead <ryan.morshead@gmail.com>
Date: Sat, 3 Jun 2023 16:20:46 -0600
Subject: [PATCH 11/37] fix poetry docs (#1003)

---
 docs/Dockerfile | 19 +++++++++++++------
 1 file changed, 13 insertions(+), 6 deletions(-)

diff --git a/docs/Dockerfile b/docs/Dockerfile
index ee28b3265..76a8ad7ee 100644
--- a/docs/Dockerfile
+++ b/docs/Dockerfile
@@ -8,9 +8,16 @@ RUN curl -sL https://deb.nodesource.com/setup_14.x  | bash -
 RUN apt-get install -yq nodejs build-essential
 RUN npm install -g npm@8.5.0
 
-# Install Pipx
-# ------------
-RUN pip install pipx
+# Install Poetry
+# --------------
+RUN pip install poetry
+
+# Create/Activate Python Venv
+# ---------------------------
+ENV VIRTUAL_ENV=/opt/venv
+RUN python3 -m venv $VIRTUAL_ENV
+ENV PATH="$VIRTUAL_ENV/bin:$PATH"
+RUN pip install --upgrade pip
 
 # Copy Files
 # ----------
@@ -22,12 +29,12 @@ COPY branding ./branding
 # Install and Build Docs
 # ----------------------
 WORKDIR /app/docs
-RUN pipx run poetry install
-RUN pipx run poetry run sphinx-build -v -W -b html source build
+RUN poetry install
+RUN sphinx-build -v -W -b html source build
 
 # Define Entrypoint
 # -----------------
 ENV PORT 5000
 ENV REACTPY_DEBUG_MODE=1
 ENV REACTPY_CHECK_VDOM_SPEC=0
-CMD pipx run poetry run python main.py
+CMD python main.py

From 4f98dcf3a1670641f0a6ecf56b28fde075b494d0 Mon Sep 17 00:00:00 2001
From: Kian-Meng Ang <kianmeng.ang@gmail.com>
Date: Tue, 6 Jun 2023 14:03:13 +0800
Subject: [PATCH 12/37] Fix typos (#1007)

* Fix typos

Found via:
- `codespell -S package-lock.json -L inflight,ue,falsy`
- `typos --format brief`

* fix capitalization

* fix capitalization

* fix capitalization

---------

Co-authored-by: Ryan Morshead <ryan.morshead@gmail.com>
---
 docs/README.md                                |  2 +-
 docs/source/_custom_js/src/index.js           |  2 +-
 docs/source/about/changelog.rst               |  4 ++--
 .../_examples/adding_state_variable/data.json |  2 +-
 .../_examples/isolated_state/data.json        |  2 +-
 .../multiple_state_variables/data.json        |  2 +-
 .../when_variables_are_not_enough/data.json   |  2 +-
 .../dangers-of-mutability/index.rst           |  4 ++--
 .../guides/adding-interactivity/index.rst     |  4 ++--
 .../multiple-state-updates/index.rst          |  4 ++--
 .../_examples/stop_event_propagation.py       |  2 +-
 .../responding-to-events/index.rst            |  4 ++--
 .../state-as-a-snapshot/index.rst             |  6 +++---
 .../html-with-reactpy/index.rst               |  2 +-
 .../distributing-javascript.rst               |  2 +-
 .../_examples/debug_error_example.py          |  2 +-
 .../representing-html.rst                     |  2 +-
 .../reference/_static/vdom-json-schema.json   |  2 +-
 docs/source/reference/html-attributes.rst     |  2 +-
 pyproject.toml                                |  2 +-
 .../tests/event-to-object.test.ts             |  2 +-
 src/py/reactpy/reactpy/_console/ast_utils.py  |  2 +-
 .../reactpy/reactpy/_console/rewrite_keys.py  |  2 +-
 src/py/reactpy/reactpy/backend/flask.py       |  2 +-
 src/py/reactpy/reactpy/config.py              |  4 ++--
 src/py/reactpy/reactpy/core/events.py         |  4 ++--
 src/py/reactpy/reactpy/core/hooks.py          |  8 ++++----
 src/py/reactpy/reactpy/core/layout.py         |  2 +-
 src/py/reactpy/reactpy/core/types.py          |  2 +-
 src/py/reactpy/reactpy/core/vdom.py           |  6 +++---
 src/py/reactpy/reactpy/html.py                |  4 ++--
 src/py/reactpy/reactpy/testing/backend.py     |  2 +-
 src/py/reactpy/reactpy/utils.py               |  2 +-
 src/py/reactpy/reactpy/web/module.py          | 20 +++++++++----------
 src/py/reactpy/tests/conftest.py              |  2 +-
 src/py/reactpy/tests/test_backend/test_all.py |  2 +-
 src/py/reactpy/tests/test_client.py           |  2 +-
 .../reactpy/tests/test_core/test_component.py |  8 ++++----
 src/py/reactpy/tests/test_core/test_events.py |  2 +-
 src/py/reactpy/tests/test_core/test_hooks.py  |  2 +-
 src/py/reactpy/tests/test_core/test_layout.py |  8 ++++----
 src/py/reactpy/tests/test_core/test_serve.py  |  2 +-
 src/py/reactpy/tests/test_core/test_vdom.py   |  4 ++--
 src/py/reactpy/tests/test_testing.py          |  2 +-
 src/py/reactpy/tests/tooling/common.py        |  2 +-
 tasks.py                                      |  2 +-
 46 files changed, 77 insertions(+), 77 deletions(-)

diff --git a/docs/README.md b/docs/README.md
index 156fe4ea1..1360bc825 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -3,7 +3,7 @@
 We provide two main ways to run the docs. Both use
 [`nox`](https://pypi.org/project/nox/):
 
-- `nox -s docs` - displayes the docs and rebuilds when files are modified.
+- `nox -s docs` - displays the docs and rebuilds when files are modified.
 - `nox -s docs-in-docker` - builds a docker image and runs the docs from there.
 
 If any changes to the core of the documentation are made (i.e. to non-`*.rst` files),
diff --git a/docs/source/_custom_js/src/index.js b/docs/source/_custom_js/src/index.js
index da04d98c7..505adedd0 100644
--- a/docs/source/_custom_js/src/index.js
+++ b/docs/source/_custom_js/src/index.js
@@ -86,7 +86,7 @@ function triggerIfInViewport(element, callback) {
     },
     {
       root: null,
-      threshold: 0.1, // set offset 0.1 means trigger if atleast 10% of element in viewport
+      threshold: 0.1, // set offset 0.1 means trigger if at least 10% of element in viewport
     },
   );
 
diff --git a/docs/source/about/changelog.rst b/docs/source/about/changelog.rst
index 3268f3739..f739ce980 100644
--- a/docs/source/about/changelog.rst
+++ b/docs/source/about/changelog.rst
@@ -62,7 +62,7 @@ v1.0.0-a4
 
 **Deprecated**
 
-- :pull:`919` - Declaration of keys via keywork arguments in standard elements. A script
+- :pull:`919` - Declaration of keys via keyword arguments in standard elements. A script
   has been added to automatically convert old usages where possible.
 
 
@@ -590,7 +590,7 @@ v0.35.2
 This release includes several bug fixes. The most significant of which is the ability to
 change the type of an element in the try (i.e. to and from being a component) without
 getting an error. Originally the errors were introduced because it was though changing
-element type would not be desireable. This was not the case though - swapping types
+element type would not be desirable. This was not the case though - swapping types
 turns out to be quite common and useful.
 
 **Closed Issues**
diff --git a/docs/source/guides/adding-interactivity/components-with-state/_examples/adding_state_variable/data.json b/docs/source/guides/adding-interactivity/components-with-state/_examples/adding_state_variable/data.json
index b2a2ba966..b1315912d 100644
--- a/docs/source/guides/adding-interactivity/components-with-state/_examples/adding_state_variable/data.json
+++ b/docs/source/guides/adding-interactivity/components-with-state/_examples/adding_state_variable/data.json
@@ -44,7 +44,7 @@
   {
     "name": "Terracotta Army",
     "artist": "Unknown Artist",
-    "description": "The Terracotta Army is a collection of terracotta sculptures depicting the armies of Qin Shi Huang, the first Emperor of China. The army consited of more than 8,000 soldiers, 130 chariots with 520 horses, and 150 cavalry horses.",
+    "description": "The Terracotta Army is a collection of terracotta sculptures depicting the armies of Qin Shi Huang, the first Emperor of China. The army consisted of more than 8,000 soldiers, 130 chariots with 520 horses, and 150 cavalry horses.",
     "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/9/9a/2015-09-22-081415_-_Terrakotta-Armee%2C_Grosse_Halle.jpg/1920px-2015-09-22-081415_-_Terrakotta-Armee%2C_Grosse_Halle.jpg",
     "alt": "12 terracotta sculptures of solemn warriors, each with a unique facial expression and armor."
   },
diff --git a/docs/source/guides/adding-interactivity/components-with-state/_examples/isolated_state/data.json b/docs/source/guides/adding-interactivity/components-with-state/_examples/isolated_state/data.json
index b2a2ba966..b1315912d 100644
--- a/docs/source/guides/adding-interactivity/components-with-state/_examples/isolated_state/data.json
+++ b/docs/source/guides/adding-interactivity/components-with-state/_examples/isolated_state/data.json
@@ -44,7 +44,7 @@
   {
     "name": "Terracotta Army",
     "artist": "Unknown Artist",
-    "description": "The Terracotta Army is a collection of terracotta sculptures depicting the armies of Qin Shi Huang, the first Emperor of China. The army consited of more than 8,000 soldiers, 130 chariots with 520 horses, and 150 cavalry horses.",
+    "description": "The Terracotta Army is a collection of terracotta sculptures depicting the armies of Qin Shi Huang, the first Emperor of China. The army consisted of more than 8,000 soldiers, 130 chariots with 520 horses, and 150 cavalry horses.",
     "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/9/9a/2015-09-22-081415_-_Terrakotta-Armee%2C_Grosse_Halle.jpg/1920px-2015-09-22-081415_-_Terrakotta-Armee%2C_Grosse_Halle.jpg",
     "alt": "12 terracotta sculptures of solemn warriors, each with a unique facial expression and armor."
   },
diff --git a/docs/source/guides/adding-interactivity/components-with-state/_examples/multiple_state_variables/data.json b/docs/source/guides/adding-interactivity/components-with-state/_examples/multiple_state_variables/data.json
index b2a2ba966..b1315912d 100644
--- a/docs/source/guides/adding-interactivity/components-with-state/_examples/multiple_state_variables/data.json
+++ b/docs/source/guides/adding-interactivity/components-with-state/_examples/multiple_state_variables/data.json
@@ -44,7 +44,7 @@
   {
     "name": "Terracotta Army",
     "artist": "Unknown Artist",
-    "description": "The Terracotta Army is a collection of terracotta sculptures depicting the armies of Qin Shi Huang, the first Emperor of China. The army consited of more than 8,000 soldiers, 130 chariots with 520 horses, and 150 cavalry horses.",
+    "description": "The Terracotta Army is a collection of terracotta sculptures depicting the armies of Qin Shi Huang, the first Emperor of China. The army consisted of more than 8,000 soldiers, 130 chariots with 520 horses, and 150 cavalry horses.",
     "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/9/9a/2015-09-22-081415_-_Terrakotta-Armee%2C_Grosse_Halle.jpg/1920px-2015-09-22-081415_-_Terrakotta-Armee%2C_Grosse_Halle.jpg",
     "alt": "12 terracotta sculptures of solemn warriors, each with a unique facial expression and armor."
   },
diff --git a/docs/source/guides/adding-interactivity/components-with-state/_examples/when_variables_are_not_enough/data.json b/docs/source/guides/adding-interactivity/components-with-state/_examples/when_variables_are_not_enough/data.json
index b2a2ba966..b1315912d 100644
--- a/docs/source/guides/adding-interactivity/components-with-state/_examples/when_variables_are_not_enough/data.json
+++ b/docs/source/guides/adding-interactivity/components-with-state/_examples/when_variables_are_not_enough/data.json
@@ -44,7 +44,7 @@
   {
     "name": "Terracotta Army",
     "artist": "Unknown Artist",
-    "description": "The Terracotta Army is a collection of terracotta sculptures depicting the armies of Qin Shi Huang, the first Emperor of China. The army consited of more than 8,000 soldiers, 130 chariots with 520 horses, and 150 cavalry horses.",
+    "description": "The Terracotta Army is a collection of terracotta sculptures depicting the armies of Qin Shi Huang, the first Emperor of China. The army consisted of more than 8,000 soldiers, 130 chariots with 520 horses, and 150 cavalry horses.",
     "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/9/9a/2015-09-22-081415_-_Terrakotta-Armee%2C_Grosse_Halle.jpg/1920px-2015-09-22-081415_-_Terrakotta-Armee%2C_Grosse_Halle.jpg",
     "alt": "12 terracotta sculptures of solemn warriors, each with a unique facial expression and armor."
   },
diff --git a/docs/source/guides/adding-interactivity/dangers-of-mutability/index.rst b/docs/source/guides/adding-interactivity/dangers-of-mutability/index.rst
index 35dfe580a..fcf817ff4 100644
--- a/docs/source/guides/adding-interactivity/dangers-of-mutability/index.rst
+++ b/docs/source/guides/adding-interactivity/dangers-of-mutability/index.rst
@@ -531,7 +531,7 @@ Removing Set Items
             s.difference_update(values)
             s -= values  # "in-place" operators mutate!
 
-            s.symetric_difference_update(values)
+            s.symmetric_difference_update(values)
             s ^= values  # "in-place" operators mutate!
 
             s.intersection_update(values)
@@ -547,7 +547,7 @@ Removing Set Items
             s.difference(values)
             s - values
 
-            s.symetric_difference(values)
+            s.symmetric_difference(values)
             s ^ values
 
             s.intersection(values)
diff --git a/docs/source/guides/adding-interactivity/index.rst b/docs/source/guides/adding-interactivity/index.rst
index 5f08bc5e4..b2beb3a81 100644
--- a/docs/source/guides/adding-interactivity/index.rst
+++ b/docs/source/guides/adding-interactivity/index.rst
@@ -111,7 +111,7 @@ Section 3: State as a Snapshot
 ------------------------------
 
 As we :ref:`learned earlier <Components with State>`, state setters behave a little
-differently than you might exepct at first glance. Instead of updating your current
+differently than you might expect at first glance. Instead of updating your current
 handle on the setter's corresponding variable, it schedules a re-render of the component
 which owns the state.
 
@@ -133,7 +133,7 @@ actually sent?
 .. reactpy:: state-as-a-snapshot/_examples/print_chat_message
 
 As it turns out, changing the message recipient after pressing send does not change
-where the message ulitmately goes. However, one could imagine a bug where the recipient
+where the message ultimately goes. However, one could imagine a bug where the recipient
 of a message is determined at the time the message is sent rather than at the time the
 "Send" button it clicked. Thus changing the recipient after pressing send would change
 where the message got sent.
diff --git a/docs/source/guides/adding-interactivity/multiple-state-updates/index.rst b/docs/source/guides/adding-interactivity/multiple-state-updates/index.rst
index 6cfc415cd..bc3245d7e 100644
--- a/docs/source/guides/adding-interactivity/multiple-state-updates/index.rst
+++ b/docs/source/guides/adding-interactivity/multiple-state-updates/index.rst
@@ -10,7 +10,7 @@ Batched Updates
 ---------------
 
 As we learned :ref:`previously <state as a snapshot>`, state variables remain fixed
-inside each render as if state were a snapshot taken at the begining of each render.
+inside each render as if state were a snapshot taken at the beginning of each render.
 This is why, in the example below, even though it might seem like clicking the
 "Increment" button would cause the ``number`` to increase by ``3``, it only does by
 ``1``:
@@ -22,7 +22,7 @@ the event handler is not an ``async`` function), ReactPy waits until all the cod
 event handler has run before processing state and starting the next render. Thus, it's
 the last call to a given state setter that matters. In the example below, even though we
 set the color of the button to ``"orange"`` and then ``"pink"`` before ``"blue"``,
-the color does not quickly flash orange and pink before blue - it alway remains blue:
+the color does not quickly flash orange and pink before blue - it always remains blue:
 
 .. reactpy:: _examples/set_color_3_times
 
diff --git a/docs/source/guides/adding-interactivity/responding-to-events/_examples/stop_event_propagation.py b/docs/source/guides/adding-interactivity/responding-to-events/_examples/stop_event_propagation.py
index 15e345bdd..41c575042 100644
--- a/docs/source/guides/adding-interactivity/responding-to-events/_examples/stop_event_propagation.py
+++ b/docs/source/guides/adding-interactivity/responding-to-events/_examples/stop_event_propagation.py
@@ -30,7 +30,7 @@ def DivInDiv():
     return html.div(
         html.button(
             {"on_click": lambda event: set_stop_propagatation(not stop_propagatation)},
-            "Toggle Propogation",
+            "Toggle Propagation",
         ),
         html.pre(f"Will propagate: {not stop_propagatation}"),
         html.pre(f"Inner click count: {inner_count}"),
diff --git a/docs/source/guides/adding-interactivity/responding-to-events/index.rst b/docs/source/guides/adding-interactivity/responding-to-events/index.rst
index 26f1c49e4..583a4a4b7 100644
--- a/docs/source/guides/adding-interactivity/responding-to-events/index.rst
+++ b/docs/source/guides/adding-interactivity/responding-to-events/index.rst
@@ -131,13 +131,13 @@ Unfortunately this means you cannot conditionally prevent default behavior in re
 to event data without writing :ref:`Custom Javascript Components`.
 
 
-Stop Event Propogation
+Stop Event Propagation
 ......................
 
 Similarly to :ref:`preventing default behavior <Preventing Default Event Actions>`, you
 can use the :func:`~reactpy.core.events.event` decorator to prevent events originating in a
 child element from propagating to parent elements by setting ``stop_propagation``. In
-the example below we place a red ``div`` inside a parent blue ``div``. When propogation
+the example below we place a red ``div`` inside a parent blue ``div``. When propagation
 is turned on, clicking the red element will cause the handler for the outer blue one to
 trigger. Conversely, when it's off, only the handler for the red element will trigger.
 
diff --git a/docs/source/guides/adding-interactivity/state-as-a-snapshot/index.rst b/docs/source/guides/adding-interactivity/state-as-a-snapshot/index.rst
index a7ede86ca..a677a3e68 100644
--- a/docs/source/guides/adding-interactivity/state-as-a-snapshot/index.rst
+++ b/docs/source/guides/adding-interactivity/state-as-a-snapshot/index.rst
@@ -17,7 +17,7 @@ version of the view rather then mutating the existing one.
 
 Given this, when ReactPy "renders" something, it's as if ReactPy has taken a snapshot of the
 UI where all the event handlers, local variables and the view itself were calculated
-using what state was present at the time of that render. Then, when user iteractions
+using what state was present at the time of that render. Then, when user interactions
 trigger state setters, ReactPy is made away of the newly set state and schedules a
 re-render. When this subsequent renders occurs it performs all the same calculations as
 before, but with this new state.
@@ -70,7 +70,7 @@ Even though, we called ``set_count`` three times with what might have seemed lik
 different values, every time we were actually just doing ``set_count(1)`` on each call.
 Only after the event handler returns will ReactPy actually perform the next render where
 count is ``1``. When it does, ``number`` will be ``1`` and we'll be able to perform the
-same subtitution as before to see what the next number will be after we click
+same substitution as before to see what the next number will be after we click
 "Increment":
 
 .. code-block::
@@ -89,7 +89,7 @@ to reason about what should happen in the example below. What will be printed wh
 
 .. reactpy:: _examples/print_count_after_set
 
-If we use the same subtitution trick we saw before, we can rewrite these lines:
+If we use the same substitution trick we saw before, we can rewrite these lines:
 
 .. code-block::
 
diff --git a/docs/source/guides/creating-interfaces/html-with-reactpy/index.rst b/docs/source/guides/creating-interfaces/html-with-reactpy/index.rst
index 99a973d94..df709148a 100644
--- a/docs/source/guides/creating-interfaces/html-with-reactpy/index.rst
+++ b/docs/source/guides/creating-interfaces/html-with-reactpy/index.rst
@@ -99,7 +99,7 @@ In ReactPy we add these attributes to elements using a dictionary:
 
 .. raw:: html
 
-    <!-- no tabindex since that would ruin accesibility of the page -->
+    <!-- no tabindex since that would ruin accessibility of the page -->
     <img
         src="https://picsum.photos/id/237/500/300"
         class="img-fluid"
diff --git a/docs/source/guides/escape-hatches/distributing-javascript.rst b/docs/source/guides/escape-hatches/distributing-javascript.rst
index 0028cee36..da9cc4b86 100644
--- a/docs/source/guides/escape-hatches/distributing-javascript.rst
+++ b/docs/source/guides/escape-hatches/distributing-javascript.rst
@@ -174,7 +174,7 @@ loaded with :func:`~reactpy.web.module.export`.
         # unique since it must share a namespace with all other javascript packages.
         name="YOUR-PACKAGE-NAME",
         file=_BUNDLE_PATH,
-        # What to temporarilly display while the module is being loaded
+        # What to temporarily display while the module is being loaded
         fallback="Loading...",
     )
 
diff --git a/docs/source/guides/getting-started/_examples/debug_error_example.py b/docs/source/guides/getting-started/_examples/debug_error_example.py
index 42d0625b1..dd0b212ab 100644
--- a/docs/source/guides/getting-started/_examples/debug_error_example.py
+++ b/docs/source/guides/getting-started/_examples/debug_error_example.py
@@ -8,7 +8,7 @@ def App():
 
 @component
 def GoodComponent():
-    return html.p("This component rendered successfuly")
+    return html.p("This component rendered successfully")
 
 
 @component
diff --git a/docs/source/guides/understanding-reactpy/representing-html.rst b/docs/source/guides/understanding-reactpy/representing-html.rst
index 5784a5a63..c2f32ebd9 100644
--- a/docs/source/guides/understanding-reactpy/representing-html.rst
+++ b/docs/source/guides/understanding-reactpy/representing-html.rst
@@ -7,7 +7,7 @@ Representing HTML 🚧
 
     Under construction 🚧
 
-We've already discussed how to contruct HTML with ReactPy in a :ref:`previous section <HTML
+We've already discussed how to construct HTML with ReactPy in a :ref:`previous section <HTML
 with ReactPy>`, but we skimmed over the question of the data structure we use to represent
 it. Let's reconsider the examples from before - on the top is some HTML and on the
 bottom is the corresponding code to create it in ReactPy:
diff --git a/docs/source/reference/_static/vdom-json-schema.json b/docs/source/reference/_static/vdom-json-schema.json
index 2281657e3..b1005d2ed 100644
--- a/docs/source/reference/_static/vdom-json-schema.json
+++ b/docs/source/reference/_static/vdom-json-schema.json
@@ -61,7 +61,7 @@
       },
       "type": ["object", "string"]
     },
-    "eventHander": {
+    "eventHandler": {
       "properties": {
         "preventDefault": {
           "type": "boolean"
diff --git a/docs/source/reference/html-attributes.rst b/docs/source/reference/html-attributes.rst
index 25530b033..967f2dfe2 100644
--- a/docs/source/reference/html-attributes.rst
+++ b/docs/source/reference/html-attributes.rst
@@ -11,7 +11,7 @@ words. For example, ``tabindex`` and ``margin-left`` become ``tab_index`` and
 ``margin_left`` respectively.
 
 
-Noteable Attributes
+Notable Attributes
 -------------------
 
 Some attributes in ReactPy are renamed, have special meaning, or are used differently
diff --git a/pyproject.toml b/pyproject.toml
index c2845ea6f..38e3acf48 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -3,7 +3,7 @@
 [project]
 name = "scripts"
 version = "0.0.0"
-description = "Scripts for managing the ReactPy respository"
+description = "Scripts for managing the ReactPy repository"
 
 # --- Hatch ----------------------------------------------------------------------------
 
diff --git a/src/js/packages/event-to-object/tests/event-to-object.test.ts b/src/js/packages/event-to-object/tests/event-to-object.test.ts
index bad3c28aa..b7b8c68af 100644
--- a/src/js/packages/event-to-object/tests/event-to-object.test.ts
+++ b/src/js/packages/event-to-object/tests/event-to-object.test.ts
@@ -57,7 +57,7 @@ const simpleTestCases: SimpleTestCase<any>[] = [
   },
   {
     types: ["copy", "cut", "paste"],
-    description: "clipboad event",
+    description: "clipboard event",
     givenEventType: window.ClipboardEvent,
     expectedConversion: { clipboardData: null },
   },
diff --git a/src/py/reactpy/reactpy/_console/ast_utils.py b/src/py/reactpy/reactpy/_console/ast_utils.py
index 180cb0e4b..220751119 100644
--- a/src/py/reactpy/reactpy/_console/ast_utils.py
+++ b/src/py/reactpy/reactpy/_console/ast_utils.py
@@ -41,7 +41,7 @@ def rewrite_changed_nodes(
             msg = "Failed to change code"
             raise RuntimeError(msg)
 
-    # check if an nodes to rewrite contain eachother, pick outermost nodes
+    # check if an nodes to rewrite contain each other, pick outermost nodes
     current_outermost_node, *sorted_nodes_to_unparse = sorted(
         nodes_to_unparse, key=lambda n: n.lineno
     )
diff --git a/src/py/reactpy/reactpy/_console/rewrite_keys.py b/src/py/reactpy/reactpy/_console/rewrite_keys.py
index cbd90351a..64ed42f33 100644
--- a/src/py/reactpy/reactpy/_console/rewrite_keys.py
+++ b/src/py/reactpy/reactpy/_console/rewrite_keys.py
@@ -40,7 +40,7 @@ def rewrite_keys(paths: list[str]) -> None:
     recommend running your code formatter like Black against your code after executing
     this command.
 
-    Additionally, We are unable to perserve the location of comments that lie within any
+    Additionally, We are unable to preserve the location of comments that lie within any
     rewritten code. This command will place the comments in the code it plans to rewrite
     just above its changes. As such it requires manual intervention to put those
     comments back in their original location.
diff --git a/src/py/reactpy/reactpy/backend/flask.py b/src/py/reactpy/reactpy/backend/flask.py
index e28ed7652..46aed3c46 100644
--- a/src/py/reactpy/reactpy/backend/flask.py
+++ b/src/py/reactpy/reactpy/backend/flask.py
@@ -106,7 +106,7 @@ def run_server() -> None:
     try:
         await stopped.wait()
     finally:
-        # we may have exitted because this task was cancelled
+        # we may have exited because this task was cancelled
         server.current.shutdown()
         # the thread should eventually join
         thread.join(timeout=3)
diff --git a/src/py/reactpy/reactpy/config.py b/src/py/reactpy/reactpy/config.py
index ace93b6d2..6dc29096c 100644
--- a/src/py/reactpy/reactpy/config.py
+++ b/src/py/reactpy/reactpy/config.py
@@ -1,6 +1,6 @@
 """
 ReactPy provides a series of configuration options that can be set using environment
-variables or, for those which allow it, a programatic interface.
+variables or, for those which allow it, a programmatic interface.
 """
 
 from pathlib import Path
@@ -49,7 +49,7 @@
 
 This directory **MUST** be treated as a black box. Downstream applications **MUST NOT**
 assume anything about the structure of this directory see :mod:`reactpy.web.module` for a
-set of publically available APIs for working with the client.
+set of publicly available APIs for working with the client.
 """
 
 REACTPY_TESTING_DEFAULT_TIMEOUT = _Option(
diff --git a/src/py/reactpy/reactpy/core/events.py b/src/py/reactpy/reactpy/core/events.py
index 51fe8d0d3..acc2077b2 100644
--- a/src/py/reactpy/reactpy/core/events.py
+++ b/src/py/reactpy/reactpy/core/events.py
@@ -44,7 +44,7 @@ def event(
         element = reactpy.html.button({"onClick": my_callback})
 
     You may want the ability to prevent the default action associated with the event
-    from taking place, or stoping the event from propagating up the DOM. This decorator
+    from taking place, or stopping the event from propagating up the DOM. This decorator
     allows you to add that functionality to your callbacks.
 
     .. code-block:: python
@@ -191,7 +191,7 @@ def merge_event_handlers(
             or handler.prevent_default != prevent_default
             or handler.target != target
         ):
-            msg = "Cannot merge handlers - 'stop_propagation', 'prevent_default' or 'target' mistmatch."
+            msg = "Cannot merge handlers - 'stop_propagation', 'prevent_default' or 'target' mismatch."
             raise ValueError(msg)
 
     return EventHandler(
diff --git a/src/py/reactpy/reactpy/core/hooks.py b/src/py/reactpy/reactpy/core/hooks.py
index 9b025c0ca..a8334458b 100644
--- a/src/py/reactpy/reactpy/core/hooks.py
+++ b/src/py/reactpy/reactpy/core/hooks.py
@@ -341,7 +341,7 @@ def use_callback(
         function:
             The function whose identity will be preserved
         dependencies:
-            Dependencies of the callback. The identity the ``function`` will be udpated
+            Dependencies of the callback. The identity the ``function`` will be updated
             if the identity of any value in the given sequence changes (i.e. their
             :func:`id` is different). By default these are inferred based on local
             variables that are referenced by the given function.
@@ -573,7 +573,7 @@ class LifeCycleHook:
             # given render have been completed.
             hook.affect_layout_did_render()
 
-            # Typically an event occurs and a new render is scheduled, thus begining
+            # Typically an event occurs and a new render is scheduled, thus beginning
             # the render cycle anew.
             hook.schedule_render()
 
@@ -626,7 +626,7 @@ def schedule_render(self) -> None:
 
     def use_state(self, function: Callable[[], _Type]) -> _Type:
         if not self._rendered_atleast_once:
-            # since we're not intialized yet we're just appending state
+            # since we're not initialized yet we're just appending state
             result = function()
             self._state += (result,)
         else:
@@ -636,7 +636,7 @@ def use_state(self, function: Callable[[], _Type]) -> _Type:
         return result
 
     def add_effect(self, effect_type: EffectType, function: Callable[[], None]) -> None:
-        """Trigger a function on the occurance of the given effect type"""
+        """Trigger a function on the occurrence of the given effect type"""
         self._event_effects[effect_type].append(function)
 
     def set_context_provider(self, provider: ContextProvider[Any]) -> None:
diff --git a/src/py/reactpy/reactpy/core/layout.py b/src/py/reactpy/reactpy/core/layout.py
index 368e79c7d..7c24e5ef7 100644
--- a/src/py/reactpy/reactpy/core/layout.py
+++ b/src/py/reactpy/reactpy/core/layout.py
@@ -568,7 +568,7 @@ def __init__(
         """The actual model of the element"""
 
         self.patch_path = patch_path
-        """A "/" delimitted path to the element within the greater layout"""
+        """A "/" delimited path to the element within the greater layout"""
 
         self.children_by_key = children_by_key
         """Child model states indexed by their unique keys"""
diff --git a/src/py/reactpy/reactpy/core/types.py b/src/py/reactpy/reactpy/core/types.py
index 93f4fe36b..45f300f4f 100644
--- a/src/py/reactpy/reactpy/core/types.py
+++ b/src/py/reactpy/reactpy/core/types.py
@@ -187,7 +187,7 @@ class EventHandlerType(Protocol):
     """Typically left as ``None`` except when a static target is useful.
 
     When testing, it may be useful to specify a static target ID so events can be
-    triggered programatically.
+    triggered programmatically.
 
     .. note::
 
diff --git a/src/py/reactpy/reactpy/core/vdom.py b/src/py/reactpy/reactpy/core/vdom.py
index c716fe347..0548c6afc 100644
--- a/src/py/reactpy/reactpy/core/vdom.py
+++ b/src/py/reactpy/reactpy/core/vdom.py
@@ -58,10 +58,10 @@
         "elementEventHandlers": {
             "type": "object",
             "patternProperties": {
-                ".*": {"$ref": "#/definitions/eventHander"},
+                ".*": {"$ref": "#/definitions/eventHandler"},
             },
         },
-        "eventHander": {
+        "eventHandler": {
             "type": "object",
             "properties": {
                 "target": {"type": "string"},
@@ -152,7 +152,7 @@ def vdom(
             iterables of children. The attribute mapping **must** precede the children,
             or children which will be merged into their respective parts of the model.
         key:
-            A string idicating the identity of a particular element. This is significant
+            A string indicating the identity of a particular element. This is significant
             to preserve event handlers across updates - without a key, a re-render would
             cause these handlers to be deleted, but with a key, they would be redirected
             to any newly defined handlers.
diff --git a/src/py/reactpy/reactpy/html.py b/src/py/reactpy/reactpy/html.py
index 0387b3135..22d318639 100644
--- a/src/py/reactpy/reactpy/html.py
+++ b/src/py/reactpy/reactpy/html.py
@@ -4,7 +4,7 @@
 
 - :func:`_`
 
-**Dcument metadata**
+**Document metadata**
 
 - :func:`base`
 - :func:`head`
@@ -309,7 +309,7 @@ def _fragment(
 _ = custom_vdom_constructor(_fragment)
 
 
-# Dcument metadata
+# Document metadata
 base = make_vdom_constructor("base")
 head = make_vdom_constructor("head")
 link = make_vdom_constructor("link")
diff --git a/src/py/reactpy/reactpy/testing/backend.py b/src/py/reactpy/reactpy/testing/backend.py
index 8e4042284..549e16056 100644
--- a/src/py/reactpy/reactpy/testing/backend.py
+++ b/src/py/reactpy/reactpy/testing/backend.py
@@ -167,7 +167,7 @@ def _hotswap(update_on_change: bool = False) -> tuple[_MountFunc, ComponentConst
     long as you set things up ahead of time.
 
     Parameters:
-        update_on_change: Whether or not all views of the layout should be udpated on a swap.
+        update_on_change: Whether or not all views of the layout should be updated on a swap.
 
     Example:
         .. code-block:: python
diff --git a/src/py/reactpy/reactpy/utils.py b/src/py/reactpy/reactpy/utils.py
index fead642f2..e5e06d98d 100644
--- a/src/py/reactpy/reactpy/utils.py
+++ b/src/py/reactpy/reactpy/utils.py
@@ -232,7 +232,7 @@ def _generate_vdom_children(
 ) -> list[VdomDict | str]:
     """Generates a list of VDOM children from an lxml node.
 
-    Inserts inner text and/or tail text inbetween VDOM children, if necessary.
+    Inserts inner text and/or tail text in between VDOM children, if necessary.
     """
     return (  # Get the inner text of the current node
         [node.text] if node.text else []
diff --git a/src/py/reactpy/reactpy/web/module.py b/src/py/reactpy/reactpy/web/module.py
index 5e615a981..48322fe24 100644
--- a/src/py/reactpy/reactpy/web/module.py
+++ b/src/py/reactpy/reactpy/web/module.py
@@ -24,7 +24,7 @@
 SourceType = NewType("SourceType", str)
 
 NAME_SOURCE = SourceType("NAME")
-"""A named souce - usually a Javascript package name"""
+"""A named source - usually a Javascript package name"""
 
 URL_SOURCE = SourceType("URL")
 """A source loaded from a URL, usually a CDN"""
@@ -44,14 +44,14 @@ def module_from_url(
             Where the javascript module will be loaded from which conforms to the
             interface for :ref:`Custom Javascript Components`
         fallback:
-            What to temporarilly display while the module is being loaded.
+            What to temporarily display while the module is being loaded.
         resolve_imports:
             Whether to try and find all the named exports of this module.
         resolve_exports_depth:
             How deeply to search for those exports.
         unmount_before_update:
             Cause the component to be unmounted before each update. This option should
-            only be used if the imported package failes to re-render when props change.
+            only be used if the imported package fails to re-render when props change.
             Using this option has negative performance consequences since all DOM
             elements must be changed on each render. See :issue:`461` for more info.
     """
@@ -110,14 +110,14 @@ def module_from_template(
         cdn:
             Where the package should be loaded from. The CDN must distribute ESM modules
         fallback:
-            What to temporarilly display while the module is being loaded.
+            What to temporarily display while the module is being loaded.
         resolve_imports:
             Whether to try and find all the named exports of this module.
         resolve_exports_depth:
             How deeply to search for those exports.
         unmount_before_update:
             Cause the component to be unmounted before each update. This option should
-            only be used if the imported package failes to re-render when props change.
+            only be used if the imported package fails to re-render when props change.
             Using this option has negative performance consequences since all DOM
             elements must be changed on each render. See :issue:`461` for more info.
     """
@@ -174,14 +174,14 @@ def module_from_file(
         file:
             The file from which the content of the web module will be created.
         fallback:
-            What to temporarilly display while the module is being loaded.
+            What to temporarily display while the module is being loaded.
         resolve_imports:
             Whether to try and find all the named exports of this module.
         resolve_exports_depth:
             How deeply to search for those exports.
         unmount_before_update:
             Cause the component to be unmounted before each update. This option should
-            only be used if the imported package failes to re-render when props change.
+            only be used if the imported package fails to re-render when props change.
             Using this option has negative performance consequences since all DOM
             elements must be changed on each render. See :issue:`461` for more info.
         symlink:
@@ -255,14 +255,14 @@ def module_from_string(
         content:
             The contents of the web module
         fallback:
-            What to temporarilly display while the module is being loaded.
+            What to temporarily display while the module is being loaded.
         resolve_imports:
             Whether to try and find all the named exports of this module.
         resolve_exports_depth:
             How deeply to search for those exports.
         unmount_before_update:
             Cause the component to be unmounted before each update. This option should
-            only be used if the imported package failes to re-render when props change.
+            only be used if the imported package fails to re-render when props change.
             Using this option has negative performance consequences since all DOM
             elements must be changed on each render. See :issue:`461` for more info.
     """
@@ -342,7 +342,7 @@ def export(
             will be returned. If a list is given, then a list of components will be
             returned.
         fallback:
-            What to temporarilly display while the module is being loaded.
+            What to temporarily display while the module is being loaded.
         allow_children:
             Whether or not these components can have children.
     """
diff --git a/src/py/reactpy/tests/conftest.py b/src/py/reactpy/tests/conftest.py
index 1a1f02098..21b23c12e 100644
--- a/src/py/reactpy/tests/conftest.py
+++ b/src/py/reactpy/tests/conftest.py
@@ -23,7 +23,7 @@ def pytest_addoption(parser: Parser) -> None:
         "--headed",
         dest="headed",
         action="store_true",
-        help="Open a browser window when runnging web-based tests",
+        help="Open a browser window when running web-based tests",
     )
 
 
diff --git a/src/py/reactpy/tests/test_backend/test_all.py b/src/py/reactpy/tests/test_backend/test_all.py
index 796f096db..11b9693a2 100644
--- a/src/py/reactpy/tests/test_backend/test_all.py
+++ b/src/py/reactpy/tests/test_backend/test_all.py
@@ -45,7 +45,7 @@ def Hello():
 
     await display.page.wait_for_selector("#hello")
 
-    # test that we can reconnect succefully
+    # test that we can reconnect successfully
     await display.page.reload()
 
     await display.page.wait_for_selector("#hello")
diff --git a/src/py/reactpy/tests/test_client.py b/src/py/reactpy/tests/test_client.py
index 5a51f8b16..3c7250e48 100644
--- a/src/py/reactpy/tests/test_client.py
+++ b/src/py/reactpy/tests/test_client.py
@@ -54,7 +54,7 @@ def SomeComponent():
             DisplayFixture(server, driver=page)
         )
 
-        # use mount instead of show to avoid a page refesh
+        # use mount instead of show to avoid a page refresh
         display.backend.mount(SomeComponent)
 
         async def get_count():
diff --git a/src/py/reactpy/tests/test_core/test_component.py b/src/py/reactpy/tests/test_core/test_component.py
index 6f40c960c..aa8996d4e 100644
--- a/src/py/reactpy/tests/test_core/test_component.py
+++ b/src/py/reactpy/tests/test_core/test_component.py
@@ -56,18 +56,18 @@ def Hello():
 
 async def test_pre_tags_are_rendered_correctly(display: DisplayFixture):
     @reactpy.component
-    def PreFormated():
+    def PreFormatted():
         return reactpy.html.pre(
             {"id": "pre-form-test"},
             reactpy.html.span("this", reactpy.html.span("is"), "some"),
-            "pre-formated",
+            "pre-formatted",
             " text",
         )
 
-    await display.show(PreFormated)
+    await display.show(PreFormatted)
 
     pre = await display.page.wait_for_selector("#pre-form-test")
 
     assert (
         await pre.evaluate("node => node.innerHTML")
-    ) == "<span>this<span>is</span>some</span>pre-formated text"
+    ) == "<span>this<span>is</span>some</span>pre-formatted text"
diff --git a/src/py/reactpy/tests/test_core/test_events.py b/src/py/reactpy/tests/test_core/test_events.py
index 67c7c2ba9..237c9d4ed 100644
--- a/src/py/reactpy/tests/test_core/test_events.py
+++ b/src/py/reactpy/tests/test_core/test_events.py
@@ -183,7 +183,7 @@ async def on_click(event):
     await display.page.wait_for_selector("#complete")
 
 
-async def test_can_stop_event_propogation(display: DisplayFixture):
+async def test_can_stop_event_propagation(display: DisplayFixture):
     clicked = reactpy.Ref(False)
 
     @reactpy.component
diff --git a/src/py/reactpy/tests/test_core/test_hooks.py b/src/py/reactpy/tests/test_core/test_hooks.py
index ce9afdc12..453d07c99 100644
--- a/src/py/reactpy/tests/test_core/test_hooks.py
+++ b/src/py/reactpy/tests/test_core/test_hooks.py
@@ -547,7 +547,7 @@ async def effect():
 
     # So I know we said the event never occurs but... to ensure the effect's future is
     # cancelled before the test is cleaned up we need to set the event. This is because
-    # the cancellation doesn't propogate before the test is resolved which causes
+    # the cancellation doesn't propagate before the test is resolved which causes
     # delayed log messages that impact other tests.
     event_that_never_occurs.set()
 
diff --git a/src/py/reactpy/tests/test_core/test_layout.py b/src/py/reactpy/tests/test_core/test_layout.py
index 4aa31fe73..3e9a40176 100644
--- a/src/py/reactpy/tests/test_core/test_layout.py
+++ b/src/py/reactpy/tests/test_core/test_layout.py
@@ -414,7 +414,7 @@ def Inner():
 
     # For some reason, holding `set_inner_component` outside the render context causes
     # the associated hook to not be automatically garbage collected. After some
-    # imperical investigation, it seems that if we do not hold `set_inner_component` in
+    # empirical investigation, it seems that if we do not hold `set_inner_component` in
     # this way, the call to `gc.collect()` isn't required. This is demonstrated in
     # `test_root_component_life_cycle_hook_is_garbage_collected`
     gc.collect()
@@ -1173,8 +1173,8 @@ def Child():
         await layout.render()
 
         # If the context provider does not render its children then internally tracked
-        # state for the removed child component might not be cleaned up propperly. This
-        # occured in the past when the context provider implemented a should_render()
+        # state for the removed child component might not be cleaned up properly. This
+        # occurred in the past when the context provider implemented a should_render()
         # method that returned False (and thus did not render its children) when the
         # context value did not change.
         toggle_remove_child()
@@ -1184,7 +1184,7 @@ def Child():
         # correctly, scheduling a render for it might cause an error.
         schedule_removed_child_render()
 
-        # If things were cleaned up propperly, the above scheduled render should not
+        # If things were cleaned up properly, the above scheduled render should not
         # actually take place. Thus we expect the timeout to occur.
         render_task = asyncio.create_task(layout.render())
         done, pending = await asyncio.wait([render_task], timeout=0.1)
diff --git a/src/py/reactpy/tests/test_core/test_serve.py b/src/py/reactpy/tests/test_core/test_serve.py
index 4d39f3e3d..64be0ec8b 100644
--- a/src/py/reactpy/tests/test_core/test_serve.py
+++ b/src/py/reactpy/tests/test_core/test_serve.py
@@ -18,7 +18,7 @@
 def make_send_recv_callbacks(events_to_inject):
     changes = []
 
-    # We need a semaphor here to simulate recieving an event after each update is sent.
+    # We need a semaphore here to simulate receiving an event after each update is sent.
     # The effect is that the send() and recv() callbacks trade off control. If we did
     # not do this, it would easy to determine when to halt because, while we might have
     # received all the events, they might not have been sent since the two callbacks are
diff --git a/src/py/reactpy/tests/test_core/test_vdom.py b/src/py/reactpy/tests/test_core/test_vdom.py
index 2aac75bb4..76e26e46f 100644
--- a/src/py/reactpy/tests/test_core/test_vdom.py
+++ b/src/py/reactpy/tests/test_core/test_vdom.py
@@ -136,7 +136,7 @@ def test_make_vdom_constructor():
             "eventHandler": {
                 "target": "something",
                 "preventDefault": False,
-                "stopPropogation": True,
+                "stopPropagation": True,
             },
         },
         {
@@ -165,7 +165,7 @@ def test_make_vdom_constructor():
             "eventHandler": {
                 "target": "something",
                 "preventDefault": False,
-                "stopPropogation": True,
+                "stopPropagation": True,
             },
             "importSource": {
                 "source": "something",
diff --git a/src/py/reactpy/tests/test_testing.py b/src/py/reactpy/tests/test_testing.py
index 760e68af3..68e36e7f6 100644
--- a/src/py/reactpy/tests/test_testing.py
+++ b/src/py/reactpy/tests/test_testing.py
@@ -11,7 +11,7 @@
 from reactpy.testing.display import DisplayFixture
 
 
-def test_assert_reactpy_logged_does_not_supress_errors():
+def test_assert_reactpy_logged_does_not_suppress_errors():
     with pytest.raises(RuntimeError, match="expected error"):
         with testing.assert_reactpy_did_log():
             msg = "expected error"
diff --git a/src/py/reactpy/tests/tooling/common.py b/src/py/reactpy/tests/tooling/common.py
index b84a3ac96..c0191bd4e 100644
--- a/src/py/reactpy/tests/tooling/common.py
+++ b/src/py/reactpy/tests/tooling/common.py
@@ -3,7 +3,7 @@
 from reactpy.core.types import LayoutEventMessage, LayoutUpdateMessage
 
 # see: https://github.com/microsoft/playwright-python/issues/1614
-DEFAULT_TYPE_DELAY = 100  # miliseconds
+DEFAULT_TYPE_DELAY = 100  # milliseconds
 
 
 def event_message(target: str, *data: Any) -> LayoutEventMessage:
diff --git a/tasks.py b/tasks.py
index 0a0097c78..a0cee5f9e 100644
--- a/tasks.py
+++ b/tasks.py
@@ -315,7 +315,7 @@ def get_current_tags(context: Context) -> set[str]:
         context.run("git diff --cached --exit-code", hide=True)
         context.run("git diff --exit-code", hide=True)
     except Exception:
-        log.error("Cannot create a tag - there are uncommited changes")
+        log.error("Cannot create a tag - there are uncommitted changes")
         return set()
 
     tags_per_commit: dict[str, list[str]] = {}

From 4f5a2517e59fa22945841d6eb3d9c8a02c028983 Mon Sep 17 00:00:00 2001
From: Ido Rot <89927111+Iddorot@users.noreply.github.com>
Date: Tue, 6 Jun 2023 20:31:38 +0300
Subject: [PATCH 13/37] Update README.md (#1009)

---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index 015249125..126367c5b 100644
--- a/README.md
+++ b/README.md
@@ -69,5 +69,5 @@ Follow the links below to find out more about this project.
 -   [Documentation](https://reactpy.dev/)
 -   [GitHub Discussions](https://github.com/reactive-python/reactpy/discussions)
 -   [Discord](https://discord.gg/uNb5P4hA9X)
--   [Contributor Guide](https://reactpy.dev/docs/developing-reactpy/contributor-guide.html)
+-   [Contributor Guide](https://reactpy.dev/docs/about/contributor-guide.html)
 -   [Code of Conduct](https://github.com/reactive-python/reactpy/blob/main/CODE_OF_CONDUCT.md)

From 309bd5a9388abc4d314da07b70a7af43d5607023 Mon Sep 17 00:00:00 2001
From: Ryan Morshead <ryan.morshead@gmail.com>
Date: Tue, 6 Jun 2023 11:49:22 -0600
Subject: [PATCH 14/37] Create CODEOWNERS (#1011)

---
 .github/CODEOWNERS | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 .github/CODEOWNERS

diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
new file mode 100644
index 000000000..3fc786dff
--- /dev/null
+++ b/.github/CODEOWNERS
@@ -0,0 +1 @@
+@maintainers

From 11732245a644a425955acea412feeb33392f9465 Mon Sep 17 00:00:00 2001
From: Ryan Morshead <ryan.morshead@gmail.com>
Date: Tue, 6 Jun 2023 16:35:05 -0600
Subject: [PATCH 15/37] Emphasize the `pip install reactpy` does not include a
 backend (#1012)

Saw a couple videos of poeple missing that fact when reading off the docs.
---
 docs/source/guides/getting-started/installing-reactpy.rst | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/docs/source/guides/getting-started/installing-reactpy.rst b/docs/source/guides/getting-started/installing-reactpy.rst
index 61356ea55..56a20aa42 100644
--- a/docs/source/guides/getting-started/installing-reactpy.rst
+++ b/docs/source/guides/getting-started/installing-reactpy.rst
@@ -9,10 +9,10 @@ backends. For example, if we want to run ReactPy using the `Starlette
 
     pip install "reactpy[starlette]"
 
-If you want to install a "pure" version of ReactPy without a backend implementation you can
-do so without any installation extras. You might do this if you wanted to :ref:`use a
-custom backend <using a custom backend>` or if you wanted to manually pin your
-dependencies:
+If you want to install a "pure" version of ReactPy **without a backend implementation**
+you can do so without any installation extras. You might do this if you wanted to
+:ref:`use a custom backend <using a custom backend>` or if you wanted to manually pin
+the dependencies for your chosen backend:
 
 .. code-block:: bash
 

From 09679e2d0de3bd18b2453950f81e14f4dc0c2861 Mon Sep 17 00:00:00 2001
From: Mark <16909269+Archmonger@users.noreply.github.com>
Date: Tue, 6 Jun 2023 16:46:28 -0700
Subject: [PATCH 16/37] Remove HR from readme (#1013)

---
 README.md | 1 -
 1 file changed, 1 deletion(-)

diff --git a/README.md b/README.md
index 126367c5b..83241e19a 100644
--- a/README.md
+++ b/README.md
@@ -18,7 +18,6 @@
     </a>
 </p>
 
----
 
 [ReactPy](https://reactpy.dev/) is a library for building user interfaces in Python without Javascript. ReactPy interfaces are made from components that look and behave similar to those found in [ReactJS](https://reactjs.org/). Designed with simplicity in mind, ReactPy can be used by those without web development experience while also being powerful enough to grow with your ambitions.
 

From 95524415d737dbd1fedcf0835a08c027dc09b18e Mon Sep 17 00:00:00 2001
From: Ryan Morshead <ryan.morshead@gmail.com>
Date: Tue, 6 Jun 2023 23:25:24 -0600
Subject: [PATCH 17/37] fix noqas (#1016)

---
 src/py/reactpy/reactpy/_warnings.py           | 2 +-
 src/py/reactpy/reactpy/backend/default.py     | 2 +-
 src/py/reactpy/tests/test_core/test_layout.py | 2 +-
 src/py/reactpy/tests/test_utils.py            | 2 +-
 4 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/py/reactpy/reactpy/_warnings.py b/src/py/reactpy/reactpy/_warnings.py
index 678b5f868..c4520604d 100644
--- a/src/py/reactpy/reactpy/_warnings.py
+++ b/src/py/reactpy/reactpy/_warnings.py
@@ -13,7 +13,7 @@ def warn(*args: Any, **kwargs: Any) -> Any:
 
 
 if TYPE_CHECKING:
-    warn = _warn  # noqa F811
+    warn = _warn  # noqa: F811
 
 
 def _frame_depth_in_module() -> int:
diff --git a/src/py/reactpy/reactpy/backend/default.py b/src/py/reactpy/reactpy/backend/default.py
index eb27ca93b..4dfeb23e8 100644
--- a/src/py/reactpy/reactpy/backend/default.py
+++ b/src/py/reactpy/reactpy/backend/default.py
@@ -50,7 +50,7 @@ async def serve_development_app(
 
 def _default_implementation() -> BackendImplementation[Any]:
     """Get the first available server implementation"""
-    global _DEFAULT_IMPLEMENTATION  # noqa PLW0603
+    global _DEFAULT_IMPLEMENTATION  # noqa: PLW0603
 
     if _DEFAULT_IMPLEMENTATION is not None:
         return _DEFAULT_IMPLEMENTATION
diff --git a/src/py/reactpy/tests/test_core/test_layout.py b/src/py/reactpy/tests/test_core/test_layout.py
index 3e9a40176..d2e1a8099 100644
--- a/src/py/reactpy/tests/test_core/test_layout.py
+++ b/src/py/reactpy/tests/test_core/test_layout.py
@@ -384,7 +384,7 @@ def wrapper(*args, **kwargs):
     @reactpy.component
     @add_to_live_hooks
     def Outer():
-        nonlocal set_inner_component  # noqa PLE0117
+        nonlocal set_inner_component
         inner_component, set_inner_component = reactpy.hooks.use_state(
             Inner(key="first")
         )
diff --git a/src/py/reactpy/tests/test_utils.py b/src/py/reactpy/tests/test_utils.py
index c764b04af..c71057f15 100644
--- a/src/py/reactpy/tests/test_utils.py
+++ b/src/py/reactpy/tests/test_utils.py
@@ -24,7 +24,7 @@ def test_basic_ref_behavior():
 
     r = reactpy.Ref()
     with pytest.raises(AttributeError):
-        r.current  # noqa B018
+        r.current  # noqa: B018
 
     r.current = 4
     assert r.current == 4

From a6eb1dce9e4b739ac3d9f922cdc06c8911e4d607 Mon Sep 17 00:00:00 2001
From: Christian Jensen <christian@orbik.com>
Date: Tue, 6 Jun 2023 22:27:10 -0700
Subject: [PATCH 18/37] Fix the Django project URL (#1015)

There must have been a rename of the repo - this is the correct on.

Co-authored-by: Ryan Morshead <ryan.morshead@gmail.com>
---
 docs/source/guides/getting-started/installing-reactpy.rst | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/docs/source/guides/getting-started/installing-reactpy.rst b/docs/source/guides/getting-started/installing-reactpy.rst
index 56a20aa42..0b2ffc28a 100644
--- a/docs/source/guides/getting-started/installing-reactpy.rst
+++ b/docs/source/guides/getting-started/installing-reactpy.rst
@@ -75,7 +75,7 @@ run ReactPy in these supported frameworks, follow the links below:
 .. grid:: 3
 
     .. grid-item-card::
-        :link: https://github.com/reactive-python/django-reactpy
+        :link: https://github.com/reactive-python/reactpy-django
         :img-background: _static/logo-django.svg
         :class-card: card-logo-image
 

From 7428340b44c8db5728fcff3b36908735ecfd4ca3 Mon Sep 17 00:00:00 2001
From: Ryan Morshead <ryan.morshead@gmail.com>
Date: Wed, 7 Jun 2023 13:42:06 -0600
Subject: [PATCH 19/37] avoid importing backend-specific dependencies (#1006)

---
 pyproject.toml                            |  1 +
 src/py/reactpy/reactpy/backend/_common.py | 77 ++++++++++++-----------
 2 files changed, 42 insertions(+), 36 deletions(-)

diff --git a/pyproject.toml b/pyproject.toml
index 38e3acf48..55208a0ef 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -26,6 +26,7 @@ dependencies = [
 [tool.hatch.envs.default.scripts]
 publish = "invoke publish {args}"
 docs = "invoke docs {args}"
+check = ["lint-py", "lint-js", "test-py", "test-js", "test-docs"]
 
 lint-py = "invoke lint-py {args}"
 lint-js = "invoke lint-js {args}"
diff --git a/src/py/reactpy/reactpy/backend/_common.py b/src/py/reactpy/reactpy/backend/_common.py
index c2e9ee583..80b4eeee1 100644
--- a/src/py/reactpy/reactpy/backend/_common.py
+++ b/src/py/reactpy/reactpy/backend/_common.py
@@ -5,10 +5,7 @@
 from collections.abc import Awaitable, Sequence
 from dataclasses import dataclass
 from pathlib import Path, PurePosixPath
-from typing import Any, cast
-
-import uvicorn
-from asgiref.typing import ASGIApplication
+from typing import TYPE_CHECKING, Any, cast
 
 from reactpy import __file__ as _reactpy_file_path
 from reactpy import html
@@ -16,6 +13,9 @@
 from reactpy.core.types import VdomDict
 from reactpy.utils import vdom_to_html
 
+if TYPE_CHECKING:
+    from asgiref.typing import ASGIApplication
+
 PATH_PREFIX = PurePosixPath("/_reactpy")
 MODULES_PATH = PATH_PREFIX / "modules"
 ASSETS_PATH = PATH_PREFIX / "assets"
@@ -23,39 +23,44 @@
 
 CLIENT_BUILD_DIR = Path(_reactpy_file_path).parent / "_static" / "app" / "dist"
 
-
-async def serve_development_asgi(
-    app: ASGIApplication | Any,
-    host: str,
-    port: int,
-    started: asyncio.Event | None,
-) -> None:
-    """Run a development server for an ASGI application"""
-    server = uvicorn.Server(
-        uvicorn.Config(
-            app,
-            host=host,
-            port=port,
-            loop="asyncio",
-            reload=True,
+try:
+    import uvicorn
+except ImportError:  # nocov
+    pass
+else:
+
+    async def serve_development_asgi(
+        app: ASGIApplication | Any,
+        host: str,
+        port: int,
+        started: asyncio.Event | None,
+    ) -> None:
+        """Run a development server for an ASGI application"""
+        server = uvicorn.Server(
+            uvicorn.Config(
+                app,
+                host=host,
+                port=port,
+                loop="asyncio",
+                reload=True,
+            )
         )
-    )
-    server.config.setup_event_loop()
-    coros: list[Awaitable[Any]] = [server.serve()]
-
-    # If a started event is provided, then use it signal based on `server.started`
-    if started:
-        coros.append(_check_if_started(server, started))
-
-    try:
-        await asyncio.gather(*coros)
-    finally:
-        # Since we aren't using the uvicorn's `run()` API, we can't guarantee uvicorn's
-        # order of operations. So we need to make sure `shutdown()` always has an initialized
-        # list of `self.servers` to use.
-        if not hasattr(server, "servers"):  # nocov
-            server.servers = []
-        await asyncio.wait_for(server.shutdown(), timeout=3)
+        server.config.setup_event_loop()
+        coros: list[Awaitable[Any]] = [server.serve()]
+
+        # If a started event is provided, then use it signal based on `server.started`
+        if started:
+            coros.append(_check_if_started(server, started))
+
+        try:
+            await asyncio.gather(*coros)
+        finally:
+            # Since we aren't using the uvicorn's `run()` API, we can't guarantee uvicorn's
+            # order of operations. So we need to make sure `shutdown()` always has an initialized
+            # list of `self.servers` to use.
+            if not hasattr(server, "servers"):  # nocov
+                server.servers = []
+            await asyncio.wait_for(server.shutdown(), timeout=3)
 
 
 async def _check_if_started(server: uvicorn.Server, started: asyncio.Event) -> None:

From 0a89ecb9ea896e9c90ccd3ffadbde679fdf3cffc Mon Sep 17 00:00:00 2001
From: Smit Parmar <smitraj333@gmail.com>
Date: Thu, 8 Jun 2023 22:21:00 +0530
Subject: [PATCH 20/37] Fixed FileNotFound error while running hatch run
 lint-py (#1023)

* Fixed FileNotFound error while running hatch run lint-py

* Revert previous changes and add minor fix
---
 tasks.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tasks.py b/tasks.py
index a0cee5f9e..e19476202 100644
--- a/tasks.py
+++ b/tasks.py
@@ -108,7 +108,7 @@ def lint_py(context: Context, fix: bool = False):
         context.run("black --check --diff .")
         in_py(
             context,
-            f"flake8 --toml-config {ROOT / 'pyproject.toml'} .",
+            f"flake8 --toml-config '{ROOT / 'pyproject.toml'}' .",
             "hatch run lint:all",
         )
 

From 9c707d33f8e87636b3717329acb81995fe7bf582 Mon Sep 17 00:00:00 2001
From: Mark Bakhit <16909269+Archmonger@users.noreply.github.com>
Date: Fri, 9 Jun 2023 12:01:08 -0700
Subject: [PATCH 21/37] Unpin reactpy client version (#1027)

---
 docs/source/guides/getting-started/_static/embed-doc-ex.html | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/docs/source/guides/getting-started/_static/embed-doc-ex.html b/docs/source/guides/getting-started/_static/embed-doc-ex.html
index 7844b31a4..589cb5d80 100644
--- a/docs/source/guides/getting-started/_static/embed-doc-ex.html
+++ b/docs/source/guides/getting-started/_static/embed-doc-ex.html
@@ -1,6 +1,6 @@
 <div id="reactpy-app" />
 <script type="module">
-  import { mountLayoutWithWebSocket } from "https://esm.sh/@reactpy/client@0.33.3";
+  import { mountLayoutWithWebSocket } from "https://esm.sh/@reactpy/client";
   mountLayoutWithWebSocket(
     document.getElementById("reactpy-app"),
     "wss://reactpy.dev/_reactpy/stream?view_id=todo"

From e7a6b5b3e13d5d8715197a4ac03832a004f98e0d Mon Sep 17 00:00:00 2001
From: Sarthak Shah <92942966+Not-Sarthak@users.noreply.github.com>
Date: Sat, 10 Jun 2023 23:37:10 +0530
Subject: [PATCH 22/37] Update index.rst (#1026)

fixed a minor typo

Co-authored-by: Ryan Morshead <ryan.morshead@gmail.com>
---
 .../guides/creating-interfaces/html-with-reactpy/index.rst      | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/docs/source/guides/creating-interfaces/html-with-reactpy/index.rst b/docs/source/guides/creating-interfaces/html-with-reactpy/index.rst
index df709148a..4a8ba4957 100644
--- a/docs/source/guides/creating-interfaces/html-with-reactpy/index.rst
+++ b/docs/source/guides/creating-interfaces/html-with-reactpy/index.rst
@@ -1,7 +1,7 @@
 HTML With ReactPy
 =================
 
-In a typical Python-base web application the responsibility of defining the view along
+In a typical Python-based web application the responsibility of defining the view along
 with its backing data and logic are distributed between a client and server
 respectively. With ReactPy, both these tasks are centralized in a single place. This is
 done by allowing HTML interfaces to be constructed in Python. Take a look at the two

From 2962ed97f7f16f84018a9328d0da3073d25d51cc Mon Sep 17 00:00:00 2001
From: Ikko Eltociear Ashimine <eltociear@gmail.com>
Date: Sun, 11 Jun 2023 03:07:59 +0900
Subject: [PATCH 23/37] docs: fix typo in distributing-javascript.rst (#1028)

creat -> create

Co-authored-by: Ryan Morshead <ryan.morshead@gmail.com>
---
 docs/source/guides/escape-hatches/distributing-javascript.rst | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/docs/source/guides/escape-hatches/distributing-javascript.rst b/docs/source/guides/escape-hatches/distributing-javascript.rst
index da9cc4b86..9eb478965 100644
--- a/docs/source/guides/escape-hatches/distributing-javascript.rst
+++ b/docs/source/guides/escape-hatches/distributing-javascript.rst
@@ -201,7 +201,7 @@ we need to declare what our build tool is (in this case Setuptools):
     requires = ["setuptools>=40.8.0", "wheel"]
     build-backend = "setuptools.build_meta"
 
-Then, we can creat the ``setup.py`` file which uses Setuptools. This will differ
+Then, we can create the ``setup.py`` file which uses Setuptools. This will differ
 substantially from a normal ``setup.py`` file since, as part of the build process we'll
 need to use NPM to bundle our Javascript. This requires customizing some of the build
 commands in Setuptools like ``build``, ``sdist``, and ``develop``:

From b1a251684d24d04fa8291d60a3267be64ae27ca1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Juan=20Jos=C3=A9=20Cano=20Duque?= <jjcadu@gmail.com>
Date: Sat, 10 Jun 2023 13:09:44 -0500
Subject: [PATCH 24/37] Update parameter name in starlette.py (#1031)

Looks like the configure() method has an error in the component parameter.

In the code documentation, the parameter indicates 'component' but the name is 'constructor', I've reviewed other files, and looks like the standard is 'component'.
---
 src/py/reactpy/reactpy/backend/starlette.py | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/py/reactpy/reactpy/backend/starlette.py b/src/py/reactpy/reactpy/backend/starlette.py
index 658fccfbd..3a9695b33 100644
--- a/src/py/reactpy/reactpy/backend/starlette.py
+++ b/src/py/reactpy/reactpy/backend/starlette.py
@@ -36,7 +36,7 @@
 
 def configure(
     app: Starlette,
-    constructor: RootComponentConstructor,
+    component: RootComponentConstructor,
     options: Options | None = None,
 ) -> None:
     """Configure the necessary ReactPy routes on the given app.
@@ -49,7 +49,7 @@ def configure(
     options = options or Options()
 
     # this route should take priority so set up it up first
-    _setup_single_view_dispatcher_route(options, app, constructor)
+    _setup_single_view_dispatcher_route(options, app, component)
 
     _setup_common_routes(options, app)
 
@@ -129,7 +129,7 @@ async def serve_index(request: Request) -> HTMLResponse:
 
 
 def _setup_single_view_dispatcher_route(
-    options: Options, app: Starlette, constructor: RootComponentConstructor
+    options: Options, app: Starlette, component: RootComponentConstructor
 ) -> None:
     @app.websocket_route(str(STREAM_PATH))
     @app.websocket_route(f"{STREAM_PATH}/{{path:path}}")
@@ -145,7 +145,7 @@ async def model_stream(socket: WebSocket) -> None:
             await serve_layout(
                 Layout(
                     ConnectionContext(
-                        constructor(),
+                        component(),
                         value=Connection(
                             scope=socket.scope,
                             location=Location(pathname, f"?{search}" if search else ""),

From 81d6343f94e3191675ec8a36166e9162420fe70b Mon Sep 17 00:00:00 2001
From: danish-mehmood <35922417+danish-mehmood@users.noreply.github.com>
Date: Sat, 10 Jun 2023 23:12:37 +0500
Subject: [PATCH 25/37] docs:fixed two typos inside Dangers of mutability
 (#1038)

Co-authored-by: danish@jedi <danish.mehmood@attech-ltd.com>
Co-authored-by: Ryan Morshead <ryan.morshead@gmail.com>
---
 .../adding-interactivity/dangers-of-mutability/index.rst      | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/docs/source/guides/adding-interactivity/dangers-of-mutability/index.rst b/docs/source/guides/adding-interactivity/dangers-of-mutability/index.rst
index fcf817ff4..bcac79e18 100644
--- a/docs/source/guides/adding-interactivity/dangers-of-mutability/index.rst
+++ b/docs/source/guides/adding-interactivity/dangers-of-mutability/index.rst
@@ -42,8 +42,8 @@ facilitate change:
     values are equivalent with the ``==`` or ``!=`` operators.
 
 Thus far, all the values we've been working with have been immutable. These include
-:class:`int`, :class:`float`, :class:`str`, and :class:`bool` values. As a result, we've
-have not had to consider the consiquences of mutations.
+:class:`int`, :class:`float`, :class:`str`, and :class:`bool` values. As a result, we
+have not had to consider the consequences of mutations.
 
 
 .. _Why Avoid Mutation:

From c80bc5169d3ad58416292aea1771b16e0e3da419 Mon Sep 17 00:00:00 2001
From: Smit Parmar <smitraj333@gmail.com>
Date: Sat, 10 Jun 2023 23:42:57 +0530
Subject: [PATCH 26/37] Fixed while running hatch run lint-js --fix (#1030)

---
 src/js/package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/js/package.json b/src/js/package.json
index dc6dc7fca..a9d84814b 100644
--- a/src/js/package.json
+++ b/src/js/package.json
@@ -4,7 +4,7 @@
     "publish": "npm --workspaces publish",
     "test": "npm --workspaces test",
     "build": "npm --workspaces run build",
-    "format": "npm run prettier -- --write && npm run eslint -- --fix",
+    "fix:format": "npm run prettier -- --write && npm run eslint -- --fix",
     "check:format": "npm run prettier -- --check && npm run eslint",
     "check:tests": "npm --workspaces run check:tests",
     "check:types": "npm --workspaces run check:types",

From f559e230dc1ac846549524dfcb722319adc1e1af Mon Sep 17 00:00:00 2001
From: Ryan Morshead <ryan.morshead@gmail.com>
Date: Sat, 10 Jun 2023 11:22:55 -0700
Subject: [PATCH 27/37] docs fixes (#1039)

---
 docs/source/reference/html-attributes.rst | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/docs/source/reference/html-attributes.rst b/docs/source/reference/html-attributes.rst
index 967f2dfe2..91813c355 100644
--- a/docs/source/reference/html-attributes.rst
+++ b/docs/source/reference/html-attributes.rst
@@ -130,7 +130,7 @@ All Attributes
 
 `aria_* <https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes>`__
   ARIA attributes let you specify the accessibility tree information for this element.
-  See ARIA attributes for a complete reference. In ReactPr, all ARIA attribute names are
+  See ARIA attributes for a complete reference. In ReactPy, all ARIA attribute names are
   exactly the same as in HTML.
 
 `auto_capitalize <https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/autocapitalize>`__
@@ -138,7 +138,7 @@ All Attributes
 
 `content_editable <https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/contenteditable>`__
   A boolean. If true, the browser lets the user edit the rendered element directly. This
-  is used to implement rich text input libraries like Lexical. ReactPr warns if you try
+  is used to implement rich text input libraries like Lexical. ReactPy warns if you try
   to pass children to an element with ``content_editable = True`` because ReactPy will
   not be able to update its content after user edits.
 

From 8120ddc2282fd51e9885220655e8a82810e52fca Mon Sep 17 00:00:00 2001
From: Abhiram <78226909+geckguy@users.noreply.github.com>
Date: Tue, 13 Jun 2023 21:39:39 +0530
Subject: [PATCH 28/37] Better Error message if no backend installed #1042
 (#1045)

* Better Error message if no backend installed #1042

* change the error message as requested

* Update src/py/reactpy/reactpy/backend/default.py

Co-authored-by: Ryan Morshead <ryan.morshead@gmail.com>

* fix linting issues

* Update src/py/reactpy/reactpy/backend/default.py

Co-authored-by: Ryan Morshead <ryan.morshead@gmail.com>

* Improve readability

* Update src/py/reactpy/reactpy/backend/default.py

* make text bold

---------

Co-authored-by: Ryan Morshead <ryan.morshead@gmail.com>
---
 src/py/reactpy/reactpy/backend/default.py | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/src/py/reactpy/reactpy/backend/default.py b/src/py/reactpy/reactpy/backend/default.py
index 4dfeb23e8..4ca192c1c 100644
--- a/src/py/reactpy/reactpy/backend/default.py
+++ b/src/py/reactpy/reactpy/backend/default.py
@@ -6,7 +6,7 @@
 from typing import Any, NoReturn
 
 from reactpy.backend.types import BackendImplementation
-from reactpy.backend.utils import all_implementations
+from reactpy.backend.utils import SUPPORTED_PACKAGES, all_implementations
 from reactpy.types import RootComponentConstructor
 
 logger = getLogger(__name__)
@@ -59,7 +59,13 @@ def _default_implementation() -> BackendImplementation[Any]:
         implementation = next(all_implementations())
     except StopIteration:  # nocov
         logger.debug("Backend implementation import failed", exc_info=exc_info())
-        msg = "No built-in server implementation installed."
+        supported_backends = ", ".join(SUPPORTED_PACKAGES)
+        msg = (
+            "It seems you haven't installed a backend. To resolve this issue, "
+            "you can install a backend by running:\n\n"
+            '\033[1mpip install "reactpy[starlette]"\033[0m\n\n'
+            f"Other supported backends include: {supported_backends}."
+        )
         raise RuntimeError(msg) from None
     else:
         _DEFAULT_IMPLEMENTATION = implementation

From 1e3141907a45fef4d7a54f1b7c4ca173de18941d Mon Sep 17 00:00:00 2001
From: Smit Parmar <smitraj333@gmail.com>
Date: Tue, 13 Jun 2023 22:37:22 +0530
Subject: [PATCH 29/37] Added pre-commit hook  (#1024)

* Added pre-commit hook

* Updated pre-commit hook

* New NL at EOF

---------

Co-authored-by: Ryan Morshead <ryan.morshead@gmail.com>
---
 .pre-commit-config.yaml | 29 +++++++++++++++++++++++++++++
 pyproject.toml          |  1 +
 2 files changed, 30 insertions(+)
 create mode 100644 .pre-commit-config.yaml

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 000000000..e4a66f532
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,29 @@
+repos:
+  - repo: local
+    hooks:
+      - id: lint-py-fix
+        name: Fix Python Lint
+        entry: hatch run lint-py --fix
+        language: system
+        pass_filenames: false
+  - repo: local
+    hooks:
+      - id: lint-py-check
+        name: Check Python Lint
+        entry: hatch run lint-py
+        language: system
+        pass_filenames: false
+  - repo: local
+    hooks:
+      - id: lint-js-fix
+        name: Fix JS Lint
+        entry: hatch run lint-js --fix
+        language: system
+        pass_filenames: false
+  - repo: local
+    hooks:
+      - id: lint-js-check
+        name: Check JS Lint
+        entry: hatch run lint-py
+        language: system
+        pass_filenames: false
diff --git a/pyproject.toml b/pyproject.toml
index 55208a0ef..a4899a495 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -21,6 +21,7 @@ dependencies = [
   # publish
   "semver >=2, <3",
   "twine",
+  "pre-commit",
 ]
 
 [tool.hatch.envs.default.scripts]

From 8785a2ab68d9d3d187c5bd580921c77474f9f02e Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 13 Jun 2023 12:16:25 -0600
Subject: [PATCH 30/37] Bump vite from 3.2.5 to 3.2.7 in /src/js/app (#1005)

Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 3.2.5 to 3.2.7.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v3.2.7/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v3.2.7/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Ryan Morshead <ryan.morshead@gmail.com>
---
 src/js/app/package-lock.json | 5086 +++-------------------------------
 src/js/app/package.json      |    2 +-
 2 files changed, 402 insertions(+), 4686 deletions(-)

diff --git a/src/js/app/package-lock.json b/src/js/app/package-lock.json
index a3a91aa30..9794c53d6 100644
--- a/src/js/app/package-lock.json
+++ b/src/js/app/package-lock.json
@@ -12,13 +12,8 @@
       "devDependencies": {
         "@types/react": "^17.0",
         "@types/react-dom": "^17.0",
-        "@typescript-eslint/eslint-plugin": "^5.58.0",
-        "@typescript-eslint/parser": "^5.58.0",
-        "eslint": "^8.38.0",
-        "eslint-plugin-react": "^7.32.2",
-        "prettier": "^3.0.0-alpha.6",
         "typescript": "^4.9.5",
-        "vite": "^3.1.8"
+        "vite": "^3.2.7"
       }
     },
     "node_modules/@esbuild/android-arm": {
@@ -53,130 +48,6 @@
         "node": ">=12"
       }
     },
-    "node_modules/@eslint-community/eslint-utils": {
-      "version": "4.4.0",
-      "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
-      "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==",
-      "dev": true,
-      "dependencies": {
-        "eslint-visitor-keys": "^3.3.0"
-      },
-      "engines": {
-        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
-      },
-      "peerDependencies": {
-        "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
-      }
-    },
-    "node_modules/@eslint-community/regexpp": {
-      "version": "4.5.0",
-      "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.5.0.tgz",
-      "integrity": "sha512-vITaYzIcNmjn5tF5uxcZ/ft7/RXGrMUIS9HalWckEOF6ESiwXKoMzAQf2UW0aVd6rnOeExTJVd5hmWXucBKGXQ==",
-      "dev": true,
-      "engines": {
-        "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
-      }
-    },
-    "node_modules/@eslint/eslintrc": {
-      "version": "2.0.2",
-      "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.2.tgz",
-      "integrity": "sha512-3W4f5tDUra+pA+FzgugqL2pRimUTDJWKr7BINqOpkZrC0uYI0NIc0/JFgBROCU07HR6GieA5m3/rsPIhDmCXTQ==",
-      "dev": true,
-      "dependencies": {
-        "ajv": "^6.12.4",
-        "debug": "^4.3.2",
-        "espree": "^9.5.1",
-        "globals": "^13.19.0",
-        "ignore": "^5.2.0",
-        "import-fresh": "^3.2.1",
-        "js-yaml": "^4.1.0",
-        "minimatch": "^3.1.2",
-        "strip-json-comments": "^3.1.1"
-      },
-      "engines": {
-        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
-      },
-      "funding": {
-        "url": "https://opencollective.com/eslint"
-      }
-    },
-    "node_modules/@eslint/js": {
-      "version": "8.38.0",
-      "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.38.0.tgz",
-      "integrity": "sha512-IoD2MfUnOV58ghIHCiil01PcohxjbYR/qCxsoC+xNgUwh1EY8jOOrYmu3d3a71+tJJ23uscEV4X2HJWMsPJu4g==",
-      "dev": true,
-      "engines": {
-        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
-      }
-    },
-    "node_modules/@humanwhocodes/config-array": {
-      "version": "0.11.8",
-      "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz",
-      "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==",
-      "dev": true,
-      "dependencies": {
-        "@humanwhocodes/object-schema": "^1.2.1",
-        "debug": "^4.1.1",
-        "minimatch": "^3.0.5"
-      },
-      "engines": {
-        "node": ">=10.10.0"
-      }
-    },
-    "node_modules/@humanwhocodes/module-importer": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
-      "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
-      "dev": true,
-      "engines": {
-        "node": ">=12.22"
-      },
-      "funding": {
-        "type": "github",
-        "url": "https://github.com/sponsors/nzakas"
-      }
-    },
-    "node_modules/@humanwhocodes/object-schema": {
-      "version": "1.2.1",
-      "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz",
-      "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
-      "dev": true
-    },
-    "node_modules/@nodelib/fs.scandir": {
-      "version": "2.1.5",
-      "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
-      "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
-      "dev": true,
-      "dependencies": {
-        "@nodelib/fs.stat": "2.0.5",
-        "run-parallel": "^1.1.9"
-      },
-      "engines": {
-        "node": ">= 8"
-      }
-    },
-    "node_modules/@nodelib/fs.stat": {
-      "version": "2.0.5",
-      "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
-      "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
-      "dev": true,
-      "engines": {
-        "node": ">= 8"
-      }
-    },
-    "node_modules/@nodelib/fs.walk": {
-      "version": "1.2.8",
-      "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
-      "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
-      "dev": true,
-      "dependencies": {
-        "@nodelib/fs.scandir": "2.1.5",
-        "fastq": "^1.6.0"
-      },
-      "engines": {
-        "node": ">= 8"
-      }
-    },
     "node_modules/@reactpy/client": {
       "version": "0.2.1",
       "resolved": "https://registry.npmjs.org/@reactpy/client/-/client-0.2.1.tgz",
@@ -190,12 +61,6 @@
         "react-dom": ">=16 <18"
       }
     },
-    "node_modules/@types/json-schema": {
-      "version": "7.0.11",
-      "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
-      "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
-      "dev": true
-    },
     "node_modules/@types/prop-types": {
       "version": "15.7.5",
       "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz",
@@ -228,2382 +93,491 @@
       "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==",
       "dev": true
     },
-    "node_modules/@types/semver": {
-      "version": "7.3.13",
-      "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz",
-      "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==",
+    "node_modules/csstype": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz",
+      "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==",
       "dev": true
     },
-    "node_modules/@typescript-eslint/eslint-plugin": {
-      "version": "5.58.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.58.0.tgz",
-      "integrity": "sha512-vxHvLhH0qgBd3/tW6/VccptSfc8FxPQIkmNTVLWcCOVqSBvqpnKkBTYrhcGlXfSnd78azwe+PsjYFj0X34/njA==",
+    "node_modules/esbuild": {
+      "version": "0.15.18",
+      "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.18.tgz",
+      "integrity": "sha512-x/R72SmW3sSFRm5zrrIjAhCeQSAWoni3CmHEqfQrZIQTM3lVCdehdwuIqaOtfC2slvpdlLa62GYoN8SxT23m6Q==",
       "dev": true,
-      "dependencies": {
-        "@eslint-community/regexpp": "^4.4.0",
-        "@typescript-eslint/scope-manager": "5.58.0",
-        "@typescript-eslint/type-utils": "5.58.0",
-        "@typescript-eslint/utils": "5.58.0",
-        "debug": "^4.3.4",
-        "grapheme-splitter": "^1.0.4",
-        "ignore": "^5.2.0",
-        "natural-compare-lite": "^1.4.0",
-        "semver": "^7.3.7",
-        "tsutils": "^3.21.0"
+      "hasInstallScript": true,
+      "bin": {
+        "esbuild": "bin/esbuild"
       },
       "engines": {
-        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/typescript-eslint"
-      },
-      "peerDependencies": {
-        "@typescript-eslint/parser": "^5.0.0",
-        "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0"
+        "node": ">=12"
       },
-      "peerDependenciesMeta": {
-        "typescript": {
-          "optional": true
-        }
+      "optionalDependencies": {
+        "@esbuild/android-arm": "0.15.18",
+        "@esbuild/linux-loong64": "0.15.18",
+        "esbuild-android-64": "0.15.18",
+        "esbuild-android-arm64": "0.15.18",
+        "esbuild-darwin-64": "0.15.18",
+        "esbuild-darwin-arm64": "0.15.18",
+        "esbuild-freebsd-64": "0.15.18",
+        "esbuild-freebsd-arm64": "0.15.18",
+        "esbuild-linux-32": "0.15.18",
+        "esbuild-linux-64": "0.15.18",
+        "esbuild-linux-arm": "0.15.18",
+        "esbuild-linux-arm64": "0.15.18",
+        "esbuild-linux-mips64le": "0.15.18",
+        "esbuild-linux-ppc64le": "0.15.18",
+        "esbuild-linux-riscv64": "0.15.18",
+        "esbuild-linux-s390x": "0.15.18",
+        "esbuild-netbsd-64": "0.15.18",
+        "esbuild-openbsd-64": "0.15.18",
+        "esbuild-sunos-64": "0.15.18",
+        "esbuild-windows-32": "0.15.18",
+        "esbuild-windows-64": "0.15.18",
+        "esbuild-windows-arm64": "0.15.18"
       }
     },
-    "node_modules/@typescript-eslint/eslint-plugin/node_modules/tsutils": {
-      "version": "3.21.0",
-      "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz",
-      "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==",
+    "node_modules/esbuild-android-64": {
+      "version": "0.15.18",
+      "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.18.tgz",
+      "integrity": "sha512-wnpt3OXRhcjfIDSZu9bnzT4/TNTDsOUvip0foZOUBG7QbSt//w3QV4FInVJxNhKc/ErhUxc5z4QjHtMi7/TbgA==",
+      "cpu": [
+        "x64"
+      ],
       "dev": true,
-      "dependencies": {
-        "tslib": "^1.8.1"
-      },
+      "optional": true,
+      "os": [
+        "android"
+      ],
       "engines": {
-        "node": ">= 6"
-      },
-      "peerDependencies": {
-        "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta"
+        "node": ">=12"
       }
     },
-    "node_modules/@typescript-eslint/parser": {
-      "version": "5.58.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.58.0.tgz",
-      "integrity": "sha512-ixaM3gRtlfrKzP8N6lRhBbjTow1t6ztfBvQNGuRM8qH1bjFFXIJ35XY+FC0RRBKn3C6cT+7VW1y8tNm7DwPHDQ==",
+    "node_modules/esbuild-android-arm64": {
+      "version": "0.15.18",
+      "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.18.tgz",
+      "integrity": "sha512-G4xu89B8FCzav9XU8EjsXacCKSG2FT7wW9J6hOc18soEHJdtWu03L3TQDGf0geNxfLTtxENKBzMSq9LlbjS8OQ==",
+      "cpu": [
+        "arm64"
+      ],
       "dev": true,
-      "dependencies": {
-        "@typescript-eslint/scope-manager": "5.58.0",
-        "@typescript-eslint/types": "5.58.0",
-        "@typescript-eslint/typescript-estree": "5.58.0",
-        "debug": "^4.3.4"
-      },
+      "optional": true,
+      "os": [
+        "android"
+      ],
       "engines": {
-        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/typescript-eslint"
-      },
-      "peerDependencies": {
-        "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0"
-      },
-      "peerDependenciesMeta": {
-        "typescript": {
-          "optional": true
-        }
+        "node": ">=12"
       }
     },
-    "node_modules/@typescript-eslint/scope-manager": {
-      "version": "5.58.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.58.0.tgz",
-      "integrity": "sha512-b+w8ypN5CFvrXWQb9Ow9T4/6LC2MikNf1viLkYTiTbkQl46CnR69w7lajz1icW0TBsYmlpg+mRzFJ4LEJ8X9NA==",
+    "node_modules/esbuild-darwin-64": {
+      "version": "0.15.18",
+      "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.18.tgz",
+      "integrity": "sha512-2WAvs95uPnVJPuYKP0Eqx+Dl/jaYseZEUUT1sjg97TJa4oBtbAKnPnl3b5M9l51/nbx7+QAEtuummJZW0sBEmg==",
+      "cpu": [
+        "x64"
+      ],
       "dev": true,
-      "dependencies": {
-        "@typescript-eslint/types": "5.58.0",
-        "@typescript-eslint/visitor-keys": "5.58.0"
-      },
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
       "engines": {
-        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/typescript-eslint"
+        "node": ">=12"
       }
     },
-    "node_modules/@typescript-eslint/type-utils": {
-      "version": "5.58.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.58.0.tgz",
-      "integrity": "sha512-FF5vP/SKAFJ+LmR9PENql7fQVVgGDOS+dq3j+cKl9iW/9VuZC/8CFmzIP0DLKXfWKpRHawJiG70rVH+xZZbp8w==",
+    "node_modules/esbuild-darwin-arm64": {
+      "version": "0.15.18",
+      "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.18.tgz",
+      "integrity": "sha512-tKPSxcTJ5OmNb1btVikATJ8NftlyNlc8BVNtyT/UAr62JFOhwHlnoPrhYWz09akBLHI9nElFVfWSTSRsrZiDUA==",
+      "cpu": [
+        "arm64"
+      ],
       "dev": true,
-      "dependencies": {
-        "@typescript-eslint/typescript-estree": "5.58.0",
-        "@typescript-eslint/utils": "5.58.0",
-        "debug": "^4.3.4",
-        "tsutils": "^3.21.0"
-      },
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
       "engines": {
-        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/typescript-eslint"
-      },
-      "peerDependencies": {
-        "eslint": "*"
-      },
-      "peerDependenciesMeta": {
-        "typescript": {
-          "optional": true
-        }
+        "node": ">=12"
       }
     },
-    "node_modules/@typescript-eslint/type-utils/node_modules/tsutils": {
-      "version": "3.21.0",
-      "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz",
-      "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==",
+    "node_modules/esbuild-freebsd-64": {
+      "version": "0.15.18",
+      "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.18.tgz",
+      "integrity": "sha512-TT3uBUxkteAjR1QbsmvSsjpKjOX6UkCstr8nMr+q7zi3NuZ1oIpa8U41Y8I8dJH2fJgdC3Dj3CXO5biLQpfdZA==",
+      "cpu": [
+        "x64"
+      ],
       "dev": true,
-      "dependencies": {
-        "tslib": "^1.8.1"
-      },
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
       "engines": {
-        "node": ">= 6"
-      },
-      "peerDependencies": {
-        "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta"
+        "node": ">=12"
       }
     },
-    "node_modules/@typescript-eslint/types": {
-      "version": "5.58.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.58.0.tgz",
-      "integrity": "sha512-JYV4eITHPzVQMnHZcYJXl2ZloC7thuUHrcUmxtzvItyKPvQ50kb9QXBkgNAt90OYMqwaodQh2kHutWZl1fc+1g==",
+    "node_modules/esbuild-freebsd-arm64": {
+      "version": "0.15.18",
+      "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.18.tgz",
+      "integrity": "sha512-R/oVr+X3Tkh+S0+tL41wRMbdWtpWB8hEAMsOXDumSSa6qJR89U0S/PpLXrGF7Wk/JykfpWNokERUpCeHDl47wA==",
+      "cpu": [
+        "arm64"
+      ],
       "dev": true,
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
       "engines": {
-        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/typescript-eslint"
+        "node": ">=12"
       }
     },
-    "node_modules/@typescript-eslint/typescript-estree": {
-      "version": "5.58.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.58.0.tgz",
-      "integrity": "sha512-cRACvGTodA+UxnYM2uwA2KCwRL7VAzo45syNysqlMyNyjw0Z35Icc9ihPJZjIYuA5bXJYiJ2YGUB59BqlOZT1Q==",
+    "node_modules/esbuild-linux-32": {
+      "version": "0.15.18",
+      "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.18.tgz",
+      "integrity": "sha512-lphF3HiCSYtaa9p1DtXndiQEeQDKPl9eN/XNoBf2amEghugNuqXNZA/ZovthNE2aa4EN43WroO0B85xVSjYkbg==",
+      "cpu": [
+        "ia32"
+      ],
       "dev": true,
-      "dependencies": {
-        "@typescript-eslint/types": "5.58.0",
-        "@typescript-eslint/visitor-keys": "5.58.0",
-        "debug": "^4.3.4",
-        "globby": "^11.1.0",
-        "is-glob": "^4.0.3",
-        "semver": "^7.3.7",
-        "tsutils": "^3.21.0"
-      },
+      "optional": true,
+      "os": [
+        "linux"
+      ],
       "engines": {
-        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/typescript-eslint"
-      },
-      "peerDependenciesMeta": {
-        "typescript": {
-          "optional": true
-        }
+        "node": ">=12"
       }
     },
-    "node_modules/@typescript-eslint/typescript-estree/node_modules/tsutils": {
-      "version": "3.21.0",
-      "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz",
-      "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==",
+    "node_modules/esbuild-linux-64": {
+      "version": "0.15.18",
+      "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.18.tgz",
+      "integrity": "sha512-hNSeP97IviD7oxLKFuii5sDPJ+QHeiFTFLoLm7NZQligur8poNOWGIgpQ7Qf8Balb69hptMZzyOBIPtY09GZYw==",
+      "cpu": [
+        "x64"
+      ],
       "dev": true,
-      "dependencies": {
-        "tslib": "^1.8.1"
-      },
+      "optional": true,
+      "os": [
+        "linux"
+      ],
       "engines": {
-        "node": ">= 6"
-      },
-      "peerDependencies": {
-        "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta"
+        "node": ">=12"
       }
     },
-    "node_modules/@typescript-eslint/utils": {
-      "version": "5.58.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.58.0.tgz",
-      "integrity": "sha512-gAmLOTFXMXOC+zP1fsqm3VceKSBQJNzV385Ok3+yzlavNHZoedajjS4UyS21gabJYcobuigQPs/z71A9MdJFqQ==",
+    "node_modules/esbuild-linux-arm": {
+      "version": "0.15.18",
+      "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.18.tgz",
+      "integrity": "sha512-UH779gstRblS4aoS2qpMl3wjg7U0j+ygu3GjIeTonCcN79ZvpPee12Qun3vcdxX+37O5LFxz39XeW2I9bybMVA==",
+      "cpu": [
+        "arm"
+      ],
       "dev": true,
-      "dependencies": {
-        "@eslint-community/eslint-utils": "^4.2.0",
-        "@types/json-schema": "^7.0.9",
-        "@types/semver": "^7.3.12",
-        "@typescript-eslint/scope-manager": "5.58.0",
-        "@typescript-eslint/types": "5.58.0",
-        "@typescript-eslint/typescript-estree": "5.58.0",
-        "eslint-scope": "^5.1.1",
-        "semver": "^7.3.7"
-      },
+      "optional": true,
+      "os": [
+        "linux"
+      ],
       "engines": {
-        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/typescript-eslint"
-      },
-      "peerDependencies": {
-        "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0"
+        "node": ">=12"
       }
     },
-    "node_modules/@typescript-eslint/utils/node_modules/eslint-scope": {
-      "version": "5.1.1",
-      "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
-      "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
+    "node_modules/esbuild-linux-arm64": {
+      "version": "0.15.18",
+      "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.18.tgz",
+      "integrity": "sha512-54qr8kg/6ilcxd+0V3h9rjT4qmjc0CccMVWrjOEM/pEcUzt8X62HfBSeZfT2ECpM7104mk4yfQXkosY8Quptug==",
+      "cpu": [
+        "arm64"
+      ],
       "dev": true,
-      "dependencies": {
-        "esrecurse": "^4.3.0",
-        "estraverse": "^4.1.1"
-      },
+      "optional": true,
+      "os": [
+        "linux"
+      ],
       "engines": {
-        "node": ">=8.0.0"
+        "node": ">=12"
       }
     },
-    "node_modules/@typescript-eslint/utils/node_modules/estraverse": {
-      "version": "4.3.0",
-      "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
-      "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
+    "node_modules/esbuild-linux-mips64le": {
+      "version": "0.15.18",
+      "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.18.tgz",
+      "integrity": "sha512-Mk6Ppwzzz3YbMl/ZZL2P0q1tnYqh/trYZ1VfNP47C31yT0K8t9s7Z077QrDA/guU60tGNp2GOwCQnp+DYv7bxQ==",
+      "cpu": [
+        "mips64el"
+      ],
       "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
       "engines": {
-        "node": ">=4.0"
+        "node": ">=12"
       }
     },
-    "node_modules/@typescript-eslint/visitor-keys": {
-      "version": "5.58.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.58.0.tgz",
-      "integrity": "sha512-/fBraTlPj0jwdyTwLyrRTxv/3lnU2H96pNTVM6z3esTWLtA5MZ9ghSMJ7Rb+TtUAdtEw9EyJzJ0EydIMKxQ9gA==",
+    "node_modules/esbuild-linux-ppc64le": {
+      "version": "0.15.18",
+      "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.18.tgz",
+      "integrity": "sha512-b0XkN4pL9WUulPTa/VKHx2wLCgvIAbgwABGnKMY19WhKZPT+8BxhZdqz6EgkqCLld7X5qiCY2F/bfpUUlnFZ9w==",
+      "cpu": [
+        "ppc64"
+      ],
       "dev": true,
-      "dependencies": {
-        "@typescript-eslint/types": "5.58.0",
-        "eslint-visitor-keys": "^3.3.0"
-      },
+      "optional": true,
+      "os": [
+        "linux"
+      ],
       "engines": {
-        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/typescript-eslint"
+        "node": ">=12"
       }
     },
-    "node_modules/acorn": {
-      "version": "8.8.2",
-      "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz",
-      "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==",
+    "node_modules/esbuild-linux-riscv64": {
+      "version": "0.15.18",
+      "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.18.tgz",
+      "integrity": "sha512-ba2COaoF5wL6VLZWn04k+ACZjZ6NYniMSQStodFKH/Pu6RxzQqzsmjR1t9QC89VYJxBeyVPTaHuBMCejl3O/xg==",
+      "cpu": [
+        "riscv64"
+      ],
       "dev": true,
-      "bin": {
-        "acorn": "bin/acorn"
-      },
+      "optional": true,
+      "os": [
+        "linux"
+      ],
       "engines": {
-        "node": ">=0.4.0"
-      }
-    },
-    "node_modules/acorn-jsx": {
-      "version": "5.3.2",
-      "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
-      "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
-      "dev": true,
-      "peerDependencies": {
-        "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+        "node": ">=12"
       }
     },
-    "node_modules/ajv": {
-      "version": "6.12.6",
-      "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
-      "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+    "node_modules/esbuild-linux-s390x": {
+      "version": "0.15.18",
+      "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.18.tgz",
+      "integrity": "sha512-VbpGuXEl5FCs1wDVp93O8UIzl3ZrglgnSQ+Hu79g7hZu6te6/YHgVJxCM2SqfIila0J3k0csfnf8VD2W7u2kzQ==",
+      "cpu": [
+        "s390x"
+      ],
       "dev": true,
-      "dependencies": {
-        "fast-deep-equal": "^3.1.1",
-        "fast-json-stable-stringify": "^2.0.0",
-        "json-schema-traverse": "^0.4.1",
-        "uri-js": "^4.2.2"
-      },
-      "funding": {
-        "type": "github",
-        "url": "https://github.com/sponsors/epoberezkin"
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
       }
     },
-    "node_modules/ansi-regex": {
-      "version": "5.0.1",
-      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
-      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+    "node_modules/esbuild-netbsd-64": {
+      "version": "0.15.18",
+      "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.18.tgz",
+      "integrity": "sha512-98ukeCdvdX7wr1vUYQzKo4kQ0N2p27H7I11maINv73fVEXt2kyh4K4m9f35U1K43Xc2QGXlzAw0K9yoU7JUjOg==",
+      "cpu": [
+        "x64"
+      ],
       "dev": true,
+      "optional": true,
+      "os": [
+        "netbsd"
+      ],
       "engines": {
-        "node": ">=8"
+        "node": ">=12"
       }
     },
-    "node_modules/ansi-styles": {
-      "version": "4.3.0",
-      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
-      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+    "node_modules/esbuild-openbsd-64": {
+      "version": "0.15.18",
+      "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.18.tgz",
+      "integrity": "sha512-yK5NCcH31Uae076AyQAXeJzt/vxIo9+omZRKj1pauhk3ITuADzuOx5N2fdHrAKPxN+zH3w96uFKlY7yIn490xQ==",
+      "cpu": [
+        "x64"
+      ],
       "dev": true,
-      "dependencies": {
-        "color-convert": "^2.0.1"
-      },
+      "optional": true,
+      "os": [
+        "openbsd"
+      ],
       "engines": {
-        "node": ">=8"
-      },
-      "funding": {
-        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+        "node": ">=12"
       }
     },
-    "node_modules/argparse": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
-      "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
-      "dev": true
-    },
-    "node_modules/array-buffer-byte-length": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz",
-      "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==",
+    "node_modules/esbuild-sunos-64": {
+      "version": "0.15.18",
+      "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.18.tgz",
+      "integrity": "sha512-On22LLFlBeLNj/YF3FT+cXcyKPEI263nflYlAhz5crxtp3yRG1Ugfr7ITyxmCmjm4vbN/dGrb/B7w7U8yJR9yw==",
+      "cpu": [
+        "x64"
+      ],
       "dev": true,
-      "dependencies": {
-        "call-bind": "^1.0.2",
-        "is-array-buffer": "^3.0.1"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
+      "optional": true,
+      "os": [
+        "sunos"
+      ],
+      "engines": {
+        "node": ">=12"
       }
     },
-    "node_modules/array-includes": {
-      "version": "3.1.6",
-      "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz",
-      "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==",
+    "node_modules/esbuild-windows-32": {
+      "version": "0.15.18",
+      "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.18.tgz",
+      "integrity": "sha512-o+eyLu2MjVny/nt+E0uPnBxYuJHBvho8vWsC2lV61A7wwTWC3jkN2w36jtA+yv1UgYkHRihPuQsL23hsCYGcOQ==",
+      "cpu": [
+        "ia32"
+      ],
       "dev": true,
-      "dependencies": {
-        "call-bind": "^1.0.2",
-        "define-properties": "^1.1.4",
-        "es-abstract": "^1.20.4",
-        "get-intrinsic": "^1.1.3",
-        "is-string": "^1.0.7"
-      },
+      "optional": true,
+      "os": [
+        "win32"
+      ],
       "engines": {
-        "node": ">= 0.4"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
+        "node": ">=12"
       }
     },
-    "node_modules/array-union": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
-      "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
+    "node_modules/esbuild-windows-64": {
+      "version": "0.15.18",
+      "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.18.tgz",
+      "integrity": "sha512-qinug1iTTaIIrCorAUjR0fcBk24fjzEedFYhhispP8Oc7SFvs+XeW3YpAKiKp8dRpizl4YYAhxMjlftAMJiaUw==",
+      "cpu": [
+        "x64"
+      ],
       "dev": true,
+      "optional": true,
+      "os": [
+        "win32"
+      ],
       "engines": {
-        "node": ">=8"
+        "node": ">=12"
       }
     },
-    "node_modules/array.prototype.flatmap": {
-      "version": "1.3.1",
-      "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz",
-      "integrity": "sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==",
+    "node_modules/esbuild-windows-arm64": {
+      "version": "0.15.18",
+      "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.18.tgz",
+      "integrity": "sha512-q9bsYzegpZcLziq0zgUi5KqGVtfhjxGbnksaBFYmWLxeV/S1fK4OLdq2DFYnXcLMjlZw2L0jLsk1eGoB522WXQ==",
+      "cpu": [
+        "arm64"
+      ],
       "dev": true,
-      "dependencies": {
-        "call-bind": "^1.0.2",
-        "define-properties": "^1.1.4",
-        "es-abstract": "^1.20.4",
-        "es-shim-unscopables": "^1.0.0"
-      },
+      "optional": true,
+      "os": [
+        "win32"
+      ],
       "engines": {
-        "node": ">= 0.4"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
+        "node": ">=12"
       }
     },
-    "node_modules/array.prototype.tosorted": {
-      "version": "1.1.1",
-      "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.1.tgz",
-      "integrity": "sha512-pZYPXPRl2PqWcsUs6LOMn+1f1532nEoPTYowBtqLwAW+W8vSVhkIGnmOX1t/UQjD6YGI0vcD2B1U7ZFGQH9jnQ==",
-      "dev": true,
+    "node_modules/event-to-object": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/event-to-object/-/event-to-object-0.1.2.tgz",
+      "integrity": "sha512-+fUmp1XOCZiYomwe5Zxp4IlchuZZfdVdjFUk5MbgRT4M+V2TEWKc0jJwKLCX/nxlJ6xM5VUb/ylzERh7YDCRrg==",
       "dependencies": {
-        "call-bind": "^1.0.2",
-        "define-properties": "^1.1.4",
-        "es-abstract": "^1.20.4",
-        "es-shim-unscopables": "^1.0.0",
-        "get-intrinsic": "^1.1.3"
+        "json-pointer": "^0.6.2"
       }
     },
-    "node_modules/available-typed-arrays": {
-      "version": "1.0.5",
-      "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz",
-      "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==",
+    "node_modules/foreach": {
+      "version": "2.0.6",
+      "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.6.tgz",
+      "integrity": "sha512-k6GAGDyqLe9JaebCsFCoudPPWfihKu8pylYXRlqP1J7ms39iPoTtk2fviNglIeQEwdh0bQeKJ01ZPyuyQvKzwg=="
+    },
+    "node_modules/fsevents": {
+      "version": "2.3.2",
+      "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+      "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
       "dev": true,
+      "hasInstallScript": true,
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
       "engines": {
-        "node": ">= 0.4"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
+        "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
       }
     },
-    "node_modules/balanced-match": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
-      "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+    "node_modules/function-bind": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+      "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
       "dev": true
     },
-    "node_modules/brace-expansion": {
-      "version": "1.1.11",
-      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
-      "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
-      "dev": true,
-      "dependencies": {
-        "balanced-match": "^1.0.0",
-        "concat-map": "0.0.1"
-      }
-    },
-    "node_modules/braces": {
-      "version": "3.0.2",
-      "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
-      "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+    "node_modules/has": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+      "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
       "dev": true,
       "dependencies": {
-        "fill-range": "^7.0.1"
+        "function-bind": "^1.1.1"
       },
       "engines": {
-        "node": ">=8"
+        "node": ">= 0.4.0"
       }
     },
-    "node_modules/call-bind": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
-      "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
+    "node_modules/is-core-module": {
+      "version": "2.12.0",
+      "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.0.tgz",
+      "integrity": "sha512-RECHCBCd/viahWmwj6enj19sKbHfJrddi/6cBDsNTKbNq0f7VeaUkBo60BqzvPqo/W54ChS62Z5qyun7cfOMqQ==",
       "dev": true,
       "dependencies": {
-        "function-bind": "^1.1.1",
-        "get-intrinsic": "^1.0.2"
+        "has": "^1.0.3"
       },
       "funding": {
         "url": "https://github.com/sponsors/ljharb"
       }
     },
-    "node_modules/callsites": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
-      "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
-      "dev": true,
-      "engines": {
-        "node": ">=6"
-      }
-    },
-    "node_modules/chalk": {
-      "version": "4.1.2",
-      "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
-      "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
-      "dev": true,
-      "dependencies": {
-        "ansi-styles": "^4.1.0",
-        "supports-color": "^7.1.0"
-      },
-      "engines": {
-        "node": ">=10"
-      },
-      "funding": {
-        "url": "https://github.com/chalk/chalk?sponsor=1"
-      }
+    "node_modules/js-tokens": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+      "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+      "peer": true
     },
-    "node_modules/color-convert": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
-      "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
-      "dev": true,
+    "node_modules/json-pointer": {
+      "version": "0.6.2",
+      "resolved": "https://registry.npmjs.org/json-pointer/-/json-pointer-0.6.2.tgz",
+      "integrity": "sha512-vLWcKbOaXlO+jvRy4qNd+TI1QUPZzfJj1tpJ3vAXDych5XJf93ftpUKe5pKCrzyIIwgBJcOcCVRUfqQP25afBw==",
       "dependencies": {
-        "color-name": "~1.1.4"
-      },
-      "engines": {
-        "node": ">=7.0.0"
-      }
-    },
-    "node_modules/color-name": {
-      "version": "1.1.4",
-      "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
-      "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
-      "dev": true
-    },
-    "node_modules/concat-map": {
-      "version": "0.0.1",
-      "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
-      "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
-      "dev": true
-    },
-    "node_modules/cross-spawn": {
-      "version": "7.0.3",
-      "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
-      "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
-      "dev": true,
-      "dependencies": {
-        "path-key": "^3.1.0",
-        "shebang-command": "^2.0.0",
-        "which": "^2.0.1"
-      },
-      "engines": {
-        "node": ">= 8"
-      }
-    },
-    "node_modules/csstype": {
-      "version": "3.1.2",
-      "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz",
-      "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==",
-      "dev": true
-    },
-    "node_modules/debug": {
-      "version": "4.3.4",
-      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
-      "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
-      "dev": true,
-      "dependencies": {
-        "ms": "2.1.2"
-      },
-      "engines": {
-        "node": ">=6.0"
-      },
-      "peerDependenciesMeta": {
-        "supports-color": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/deep-is": {
-      "version": "0.1.4",
-      "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
-      "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
-      "dev": true
-    },
-    "node_modules/define-properties": {
-      "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz",
-      "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==",
-      "dev": true,
-      "dependencies": {
-        "has-property-descriptors": "^1.0.0",
-        "object-keys": "^1.1.1"
-      },
-      "engines": {
-        "node": ">= 0.4"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/dir-glob": {
-      "version": "3.0.1",
-      "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
-      "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
-      "dev": true,
-      "dependencies": {
-        "path-type": "^4.0.0"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/doctrine": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
-      "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
-      "dev": true,
-      "dependencies": {
-        "esutils": "^2.0.2"
-      },
-      "engines": {
-        "node": ">=6.0.0"
-      }
-    },
-    "node_modules/es-abstract": {
-      "version": "1.21.2",
-      "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.2.tgz",
-      "integrity": "sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg==",
-      "dev": true,
-      "dependencies": {
-        "array-buffer-byte-length": "^1.0.0",
-        "available-typed-arrays": "^1.0.5",
-        "call-bind": "^1.0.2",
-        "es-set-tostringtag": "^2.0.1",
-        "es-to-primitive": "^1.2.1",
-        "function.prototype.name": "^1.1.5",
-        "get-intrinsic": "^1.2.0",
-        "get-symbol-description": "^1.0.0",
-        "globalthis": "^1.0.3",
-        "gopd": "^1.0.1",
-        "has": "^1.0.3",
-        "has-property-descriptors": "^1.0.0",
-        "has-proto": "^1.0.1",
-        "has-symbols": "^1.0.3",
-        "internal-slot": "^1.0.5",
-        "is-array-buffer": "^3.0.2",
-        "is-callable": "^1.2.7",
-        "is-negative-zero": "^2.0.2",
-        "is-regex": "^1.1.4",
-        "is-shared-array-buffer": "^1.0.2",
-        "is-string": "^1.0.7",
-        "is-typed-array": "^1.1.10",
-        "is-weakref": "^1.0.2",
-        "object-inspect": "^1.12.3",
-        "object-keys": "^1.1.1",
-        "object.assign": "^4.1.4",
-        "regexp.prototype.flags": "^1.4.3",
-        "safe-regex-test": "^1.0.0",
-        "string.prototype.trim": "^1.2.7",
-        "string.prototype.trimend": "^1.0.6",
-        "string.prototype.trimstart": "^1.0.6",
-        "typed-array-length": "^1.0.4",
-        "unbox-primitive": "^1.0.2",
-        "which-typed-array": "^1.1.9"
-      },
-      "engines": {
-        "node": ">= 0.4"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/es-set-tostringtag": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz",
-      "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==",
-      "dev": true,
-      "dependencies": {
-        "get-intrinsic": "^1.1.3",
-        "has": "^1.0.3",
-        "has-tostringtag": "^1.0.0"
-      },
-      "engines": {
-        "node": ">= 0.4"
-      }
-    },
-    "node_modules/es-shim-unscopables": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz",
-      "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==",
-      "dev": true,
-      "dependencies": {
-        "has": "^1.0.3"
-      }
-    },
-    "node_modules/es-to-primitive": {
-      "version": "1.2.1",
-      "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz",
-      "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==",
-      "dev": true,
-      "dependencies": {
-        "is-callable": "^1.1.4",
-        "is-date-object": "^1.0.1",
-        "is-symbol": "^1.0.2"
-      },
-      "engines": {
-        "node": ">= 0.4"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/esbuild": {
-      "version": "0.15.18",
-      "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.18.tgz",
-      "integrity": "sha512-x/R72SmW3sSFRm5zrrIjAhCeQSAWoni3CmHEqfQrZIQTM3lVCdehdwuIqaOtfC2slvpdlLa62GYoN8SxT23m6Q==",
-      "dev": true,
-      "hasInstallScript": true,
-      "bin": {
-        "esbuild": "bin/esbuild"
-      },
-      "engines": {
-        "node": ">=12"
-      },
-      "optionalDependencies": {
-        "@esbuild/android-arm": "0.15.18",
-        "@esbuild/linux-loong64": "0.15.18",
-        "esbuild-android-64": "0.15.18",
-        "esbuild-android-arm64": "0.15.18",
-        "esbuild-darwin-64": "0.15.18",
-        "esbuild-darwin-arm64": "0.15.18",
-        "esbuild-freebsd-64": "0.15.18",
-        "esbuild-freebsd-arm64": "0.15.18",
-        "esbuild-linux-32": "0.15.18",
-        "esbuild-linux-64": "0.15.18",
-        "esbuild-linux-arm": "0.15.18",
-        "esbuild-linux-arm64": "0.15.18",
-        "esbuild-linux-mips64le": "0.15.18",
-        "esbuild-linux-ppc64le": "0.15.18",
-        "esbuild-linux-riscv64": "0.15.18",
-        "esbuild-linux-s390x": "0.15.18",
-        "esbuild-netbsd-64": "0.15.18",
-        "esbuild-openbsd-64": "0.15.18",
-        "esbuild-sunos-64": "0.15.18",
-        "esbuild-windows-32": "0.15.18",
-        "esbuild-windows-64": "0.15.18",
-        "esbuild-windows-arm64": "0.15.18"
-      }
-    },
-    "node_modules/esbuild-android-64": {
-      "version": "0.15.18",
-      "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.18.tgz",
-      "integrity": "sha512-wnpt3OXRhcjfIDSZu9bnzT4/TNTDsOUvip0foZOUBG7QbSt//w3QV4FInVJxNhKc/ErhUxc5z4QjHtMi7/TbgA==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "android"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/esbuild-android-arm64": {
-      "version": "0.15.18",
-      "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.18.tgz",
-      "integrity": "sha512-G4xu89B8FCzav9XU8EjsXacCKSG2FT7wW9J6hOc18soEHJdtWu03L3TQDGf0geNxfLTtxENKBzMSq9LlbjS8OQ==",
-      "cpu": [
-        "arm64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "android"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/esbuild-darwin-64": {
-      "version": "0.15.18",
-      "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.18.tgz",
-      "integrity": "sha512-2WAvs95uPnVJPuYKP0Eqx+Dl/jaYseZEUUT1sjg97TJa4oBtbAKnPnl3b5M9l51/nbx7+QAEtuummJZW0sBEmg==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "darwin"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/esbuild-darwin-arm64": {
-      "version": "0.15.18",
-      "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.18.tgz",
-      "integrity": "sha512-tKPSxcTJ5OmNb1btVikATJ8NftlyNlc8BVNtyT/UAr62JFOhwHlnoPrhYWz09akBLHI9nElFVfWSTSRsrZiDUA==",
-      "cpu": [
-        "arm64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "darwin"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/esbuild-freebsd-64": {
-      "version": "0.15.18",
-      "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.18.tgz",
-      "integrity": "sha512-TT3uBUxkteAjR1QbsmvSsjpKjOX6UkCstr8nMr+q7zi3NuZ1oIpa8U41Y8I8dJH2fJgdC3Dj3CXO5biLQpfdZA==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "freebsd"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/esbuild-freebsd-arm64": {
-      "version": "0.15.18",
-      "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.18.tgz",
-      "integrity": "sha512-R/oVr+X3Tkh+S0+tL41wRMbdWtpWB8hEAMsOXDumSSa6qJR89U0S/PpLXrGF7Wk/JykfpWNokERUpCeHDl47wA==",
-      "cpu": [
-        "arm64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "freebsd"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/esbuild-linux-32": {
-      "version": "0.15.18",
-      "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.18.tgz",
-      "integrity": "sha512-lphF3HiCSYtaa9p1DtXndiQEeQDKPl9eN/XNoBf2amEghugNuqXNZA/ZovthNE2aa4EN43WroO0B85xVSjYkbg==",
-      "cpu": [
-        "ia32"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/esbuild-linux-64": {
-      "version": "0.15.18",
-      "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.18.tgz",
-      "integrity": "sha512-hNSeP97IviD7oxLKFuii5sDPJ+QHeiFTFLoLm7NZQligur8poNOWGIgpQ7Qf8Balb69hptMZzyOBIPtY09GZYw==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/esbuild-linux-arm": {
-      "version": "0.15.18",
-      "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.18.tgz",
-      "integrity": "sha512-UH779gstRblS4aoS2qpMl3wjg7U0j+ygu3GjIeTonCcN79ZvpPee12Qun3vcdxX+37O5LFxz39XeW2I9bybMVA==",
-      "cpu": [
-        "arm"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/esbuild-linux-arm64": {
-      "version": "0.15.18",
-      "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.18.tgz",
-      "integrity": "sha512-54qr8kg/6ilcxd+0V3h9rjT4qmjc0CccMVWrjOEM/pEcUzt8X62HfBSeZfT2ECpM7104mk4yfQXkosY8Quptug==",
-      "cpu": [
-        "arm64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/esbuild-linux-mips64le": {
-      "version": "0.15.18",
-      "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.18.tgz",
-      "integrity": "sha512-Mk6Ppwzzz3YbMl/ZZL2P0q1tnYqh/trYZ1VfNP47C31yT0K8t9s7Z077QrDA/guU60tGNp2GOwCQnp+DYv7bxQ==",
-      "cpu": [
-        "mips64el"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/esbuild-linux-ppc64le": {
-      "version": "0.15.18",
-      "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.18.tgz",
-      "integrity": "sha512-b0XkN4pL9WUulPTa/VKHx2wLCgvIAbgwABGnKMY19WhKZPT+8BxhZdqz6EgkqCLld7X5qiCY2F/bfpUUlnFZ9w==",
-      "cpu": [
-        "ppc64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/esbuild-linux-riscv64": {
-      "version": "0.15.18",
-      "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.18.tgz",
-      "integrity": "sha512-ba2COaoF5wL6VLZWn04k+ACZjZ6NYniMSQStodFKH/Pu6RxzQqzsmjR1t9QC89VYJxBeyVPTaHuBMCejl3O/xg==",
-      "cpu": [
-        "riscv64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/esbuild-linux-s390x": {
-      "version": "0.15.18",
-      "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.18.tgz",
-      "integrity": "sha512-VbpGuXEl5FCs1wDVp93O8UIzl3ZrglgnSQ+Hu79g7hZu6te6/YHgVJxCM2SqfIila0J3k0csfnf8VD2W7u2kzQ==",
-      "cpu": [
-        "s390x"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/esbuild-netbsd-64": {
-      "version": "0.15.18",
-      "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.18.tgz",
-      "integrity": "sha512-98ukeCdvdX7wr1vUYQzKo4kQ0N2p27H7I11maINv73fVEXt2kyh4K4m9f35U1K43Xc2QGXlzAw0K9yoU7JUjOg==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "netbsd"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/esbuild-openbsd-64": {
-      "version": "0.15.18",
-      "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.18.tgz",
-      "integrity": "sha512-yK5NCcH31Uae076AyQAXeJzt/vxIo9+omZRKj1pauhk3ITuADzuOx5N2fdHrAKPxN+zH3w96uFKlY7yIn490xQ==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "openbsd"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/esbuild-sunos-64": {
-      "version": "0.15.18",
-      "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.18.tgz",
-      "integrity": "sha512-On22LLFlBeLNj/YF3FT+cXcyKPEI263nflYlAhz5crxtp3yRG1Ugfr7ITyxmCmjm4vbN/dGrb/B7w7U8yJR9yw==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "sunos"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/esbuild-windows-32": {
-      "version": "0.15.18",
-      "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.18.tgz",
-      "integrity": "sha512-o+eyLu2MjVny/nt+E0uPnBxYuJHBvho8vWsC2lV61A7wwTWC3jkN2w36jtA+yv1UgYkHRihPuQsL23hsCYGcOQ==",
-      "cpu": [
-        "ia32"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "win32"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/esbuild-windows-64": {
-      "version": "0.15.18",
-      "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.18.tgz",
-      "integrity": "sha512-qinug1iTTaIIrCorAUjR0fcBk24fjzEedFYhhispP8Oc7SFvs+XeW3YpAKiKp8dRpizl4YYAhxMjlftAMJiaUw==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "win32"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/esbuild-windows-arm64": {
-      "version": "0.15.18",
-      "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.18.tgz",
-      "integrity": "sha512-q9bsYzegpZcLziq0zgUi5KqGVtfhjxGbnksaBFYmWLxeV/S1fK4OLdq2DFYnXcLMjlZw2L0jLsk1eGoB522WXQ==",
-      "cpu": [
-        "arm64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "win32"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/escape-string-regexp": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
-      "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
-      "dev": true,
-      "engines": {
-        "node": ">=10"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
-      }
-    },
-    "node_modules/eslint": {
-      "version": "8.38.0",
-      "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.38.0.tgz",
-      "integrity": "sha512-pIdsD2jwlUGf/U38Jv97t8lq6HpaU/G9NKbYmpWpZGw3LdTNhZLbJePqxOXGB5+JEKfOPU/XLxYxFh03nr1KTg==",
-      "dev": true,
-      "dependencies": {
-        "@eslint-community/eslint-utils": "^4.2.0",
-        "@eslint-community/regexpp": "^4.4.0",
-        "@eslint/eslintrc": "^2.0.2",
-        "@eslint/js": "8.38.0",
-        "@humanwhocodes/config-array": "^0.11.8",
-        "@humanwhocodes/module-importer": "^1.0.1",
-        "@nodelib/fs.walk": "^1.2.8",
-        "ajv": "^6.10.0",
-        "chalk": "^4.0.0",
-        "cross-spawn": "^7.0.2",
-        "debug": "^4.3.2",
-        "doctrine": "^3.0.0",
-        "escape-string-regexp": "^4.0.0",
-        "eslint-scope": "^7.1.1",
-        "eslint-visitor-keys": "^3.4.0",
-        "espree": "^9.5.1",
-        "esquery": "^1.4.2",
-        "esutils": "^2.0.2",
-        "fast-deep-equal": "^3.1.3",
-        "file-entry-cache": "^6.0.1",
-        "find-up": "^5.0.0",
-        "glob-parent": "^6.0.2",
-        "globals": "^13.19.0",
-        "grapheme-splitter": "^1.0.4",
-        "ignore": "^5.2.0",
-        "import-fresh": "^3.0.0",
-        "imurmurhash": "^0.1.4",
-        "is-glob": "^4.0.0",
-        "is-path-inside": "^3.0.3",
-        "js-sdsl": "^4.1.4",
-        "js-yaml": "^4.1.0",
-        "json-stable-stringify-without-jsonify": "^1.0.1",
-        "levn": "^0.4.1",
-        "lodash.merge": "^4.6.2",
-        "minimatch": "^3.1.2",
-        "natural-compare": "^1.4.0",
-        "optionator": "^0.9.1",
-        "strip-ansi": "^6.0.1",
-        "strip-json-comments": "^3.1.0",
-        "text-table": "^0.2.0"
-      },
-      "bin": {
-        "eslint": "bin/eslint.js"
-      },
-      "engines": {
-        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
-      },
-      "funding": {
-        "url": "https://opencollective.com/eslint"
-      }
-    },
-    "node_modules/eslint-plugin-react": {
-      "version": "7.32.2",
-      "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.32.2.tgz",
-      "integrity": "sha512-t2fBMa+XzonrrNkyVirzKlvn5RXzzPwRHtMvLAtVZrt8oxgnTQaYbU6SXTOO1mwQgp1y5+toMSKInnzGr0Knqg==",
-      "dev": true,
-      "dependencies": {
-        "array-includes": "^3.1.6",
-        "array.prototype.flatmap": "^1.3.1",
-        "array.prototype.tosorted": "^1.1.1",
-        "doctrine": "^2.1.0",
-        "estraverse": "^5.3.0",
-        "jsx-ast-utils": "^2.4.1 || ^3.0.0",
-        "minimatch": "^3.1.2",
-        "object.entries": "^1.1.6",
-        "object.fromentries": "^2.0.6",
-        "object.hasown": "^1.1.2",
-        "object.values": "^1.1.6",
-        "prop-types": "^15.8.1",
-        "resolve": "^2.0.0-next.4",
-        "semver": "^6.3.0",
-        "string.prototype.matchall": "^4.0.8"
-      },
-      "engines": {
-        "node": ">=4"
-      },
-      "peerDependencies": {
-        "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8"
-      }
-    },
-    "node_modules/eslint-plugin-react/node_modules/doctrine": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
-      "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==",
-      "dev": true,
-      "dependencies": {
-        "esutils": "^2.0.2"
-      },
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
-    "node_modules/eslint-plugin-react/node_modules/resolve": {
-      "version": "2.0.0-next.4",
-      "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.4.tgz",
-      "integrity": "sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==",
-      "dev": true,
-      "dependencies": {
-        "is-core-module": "^2.9.0",
-        "path-parse": "^1.0.7",
-        "supports-preserve-symlinks-flag": "^1.0.0"
-      },
-      "bin": {
-        "resolve": "bin/resolve"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/eslint-plugin-react/node_modules/semver": {
-      "version": "6.3.0",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
-      "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
-      "dev": true,
-      "bin": {
-        "semver": "bin/semver.js"
-      }
-    },
-    "node_modules/eslint-scope": {
-      "version": "7.1.1",
-      "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz",
-      "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==",
-      "dev": true,
-      "dependencies": {
-        "esrecurse": "^4.3.0",
-        "estraverse": "^5.2.0"
-      },
-      "engines": {
-        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
-      }
-    },
-    "node_modules/eslint-visitor-keys": {
-      "version": "3.4.0",
-      "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz",
-      "integrity": "sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ==",
-      "dev": true,
-      "engines": {
-        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
-      },
-      "funding": {
-        "url": "https://opencollective.com/eslint"
-      }
-    },
-    "node_modules/espree": {
-      "version": "9.5.1",
-      "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.1.tgz",
-      "integrity": "sha512-5yxtHSZXRSW5pvv3hAlXM5+/Oswi1AUFqBmbibKb5s6bp3rGIDkyXU6xCoyuuLhijr4SFwPrXRoZjz0AZDN9tg==",
-      "dev": true,
-      "dependencies": {
-        "acorn": "^8.8.0",
-        "acorn-jsx": "^5.3.2",
-        "eslint-visitor-keys": "^3.4.0"
-      },
-      "engines": {
-        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
-      },
-      "funding": {
-        "url": "https://opencollective.com/eslint"
-      }
-    },
-    "node_modules/esquery": {
-      "version": "1.5.0",
-      "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz",
-      "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==",
-      "dev": true,
-      "dependencies": {
-        "estraverse": "^5.1.0"
-      },
-      "engines": {
-        "node": ">=0.10"
-      }
-    },
-    "node_modules/esrecurse": {
-      "version": "4.3.0",
-      "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
-      "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
-      "dev": true,
-      "dependencies": {
-        "estraverse": "^5.2.0"
-      },
-      "engines": {
-        "node": ">=4.0"
-      }
-    },
-    "node_modules/estraverse": {
-      "version": "5.3.0",
-      "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
-      "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
-      "dev": true,
-      "engines": {
-        "node": ">=4.0"
-      }
-    },
-    "node_modules/esutils": {
-      "version": "2.0.3",
-      "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
-      "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
-      "dev": true,
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
-    "node_modules/event-to-object": {
-      "version": "0.1.2",
-      "resolved": "https://registry.npmjs.org/event-to-object/-/event-to-object-0.1.2.tgz",
-      "integrity": "sha512-+fUmp1XOCZiYomwe5Zxp4IlchuZZfdVdjFUk5MbgRT4M+V2TEWKc0jJwKLCX/nxlJ6xM5VUb/ylzERh7YDCRrg==",
-      "dependencies": {
-        "json-pointer": "^0.6.2"
-      }
-    },
-    "node_modules/fast-deep-equal": {
-      "version": "3.1.3",
-      "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
-      "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
-      "dev": true
-    },
-    "node_modules/fast-glob": {
-      "version": "3.2.12",
-      "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz",
-      "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==",
-      "dev": true,
-      "dependencies": {
-        "@nodelib/fs.stat": "^2.0.2",
-        "@nodelib/fs.walk": "^1.2.3",
-        "glob-parent": "^5.1.2",
-        "merge2": "^1.3.0",
-        "micromatch": "^4.0.4"
-      },
-      "engines": {
-        "node": ">=8.6.0"
-      }
-    },
-    "node_modules/fast-glob/node_modules/glob-parent": {
-      "version": "5.1.2",
-      "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
-      "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
-      "dev": true,
-      "dependencies": {
-        "is-glob": "^4.0.1"
-      },
-      "engines": {
-        "node": ">= 6"
-      }
-    },
-    "node_modules/fast-json-stable-stringify": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
-      "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
-      "dev": true
-    },
-    "node_modules/fast-levenshtein": {
-      "version": "2.0.6",
-      "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
-      "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
-      "dev": true
-    },
-    "node_modules/fastq": {
-      "version": "1.15.0",
-      "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz",
-      "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==",
-      "dev": true,
-      "dependencies": {
-        "reusify": "^1.0.4"
-      }
-    },
-    "node_modules/file-entry-cache": {
-      "version": "6.0.1",
-      "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
-      "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
-      "dev": true,
-      "dependencies": {
-        "flat-cache": "^3.0.4"
-      },
-      "engines": {
-        "node": "^10.12.0 || >=12.0.0"
-      }
-    },
-    "node_modules/fill-range": {
-      "version": "7.0.1",
-      "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
-      "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
-      "dev": true,
-      "dependencies": {
-        "to-regex-range": "^5.0.1"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/find-up": {
-      "version": "5.0.0",
-      "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
-      "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
-      "dev": true,
-      "dependencies": {
-        "locate-path": "^6.0.0",
-        "path-exists": "^4.0.0"
-      },
-      "engines": {
-        "node": ">=10"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
-      }
-    },
-    "node_modules/flat-cache": {
-      "version": "3.0.4",
-      "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz",
-      "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==",
-      "dev": true,
-      "dependencies": {
-        "flatted": "^3.1.0",
-        "rimraf": "^3.0.2"
-      },
-      "engines": {
-        "node": "^10.12.0 || >=12.0.0"
-      }
-    },
-    "node_modules/flatted": {
-      "version": "3.2.7",
-      "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz",
-      "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==",
-      "dev": true
-    },
-    "node_modules/for-each": {
-      "version": "0.3.3",
-      "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz",
-      "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==",
-      "dev": true,
-      "dependencies": {
-        "is-callable": "^1.1.3"
-      }
-    },
-    "node_modules/foreach": {
-      "version": "2.0.6",
-      "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.6.tgz",
-      "integrity": "sha512-k6GAGDyqLe9JaebCsFCoudPPWfihKu8pylYXRlqP1J7ms39iPoTtk2fviNglIeQEwdh0bQeKJ01ZPyuyQvKzwg=="
-    },
-    "node_modules/fs.realpath": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
-      "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
-      "dev": true
-    },
-    "node_modules/fsevents": {
-      "version": "2.3.2",
-      "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
-      "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
-      "dev": true,
-      "hasInstallScript": true,
-      "optional": true,
-      "os": [
-        "darwin"
-      ],
-      "engines": {
-        "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
-      }
-    },
-    "node_modules/function-bind": {
-      "version": "1.1.1",
-      "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
-      "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
-      "dev": true
-    },
-    "node_modules/function.prototype.name": {
-      "version": "1.1.5",
-      "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz",
-      "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==",
-      "dev": true,
-      "dependencies": {
-        "call-bind": "^1.0.2",
-        "define-properties": "^1.1.3",
-        "es-abstract": "^1.19.0",
-        "functions-have-names": "^1.2.2"
-      },
-      "engines": {
-        "node": ">= 0.4"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/functions-have-names": {
-      "version": "1.2.3",
-      "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz",
-      "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==",
-      "dev": true,
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/get-intrinsic": {
-      "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz",
-      "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==",
-      "dev": true,
-      "dependencies": {
-        "function-bind": "^1.1.1",
-        "has": "^1.0.3",
-        "has-symbols": "^1.0.3"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/get-symbol-description": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz",
-      "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==",
-      "dev": true,
-      "dependencies": {
-        "call-bind": "^1.0.2",
-        "get-intrinsic": "^1.1.1"
-      },
-      "engines": {
-        "node": ">= 0.4"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/glob": {
-      "version": "7.2.3",
-      "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
-      "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
-      "dev": true,
-      "dependencies": {
-        "fs.realpath": "^1.0.0",
-        "inflight": "^1.0.4",
-        "inherits": "2",
-        "minimatch": "^3.1.1",
-        "once": "^1.3.0",
-        "path-is-absolute": "^1.0.0"
-      },
-      "engines": {
-        "node": "*"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/isaacs"
-      }
-    },
-    "node_modules/glob-parent": {
-      "version": "6.0.2",
-      "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
-      "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
-      "dev": true,
-      "dependencies": {
-        "is-glob": "^4.0.3"
-      },
-      "engines": {
-        "node": ">=10.13.0"
-      }
-    },
-    "node_modules/globals": {
-      "version": "13.20.0",
-      "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz",
-      "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==",
-      "dev": true,
-      "dependencies": {
-        "type-fest": "^0.20.2"
-      },
-      "engines": {
-        "node": ">=8"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
-      }
-    },
-    "node_modules/globalthis": {
-      "version": "1.0.3",
-      "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz",
-      "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==",
-      "dev": true,
-      "dependencies": {
-        "define-properties": "^1.1.3"
-      },
-      "engines": {
-        "node": ">= 0.4"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/globby": {
-      "version": "11.1.0",
-      "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz",
-      "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==",
-      "dev": true,
-      "dependencies": {
-        "array-union": "^2.1.0",
-        "dir-glob": "^3.0.1",
-        "fast-glob": "^3.2.9",
-        "ignore": "^5.2.0",
-        "merge2": "^1.4.1",
-        "slash": "^3.0.0"
-      },
-      "engines": {
-        "node": ">=10"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
-      }
-    },
-    "node_modules/gopd": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
-      "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
-      "dev": true,
-      "dependencies": {
-        "get-intrinsic": "^1.1.3"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/grapheme-splitter": {
-      "version": "1.0.4",
-      "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz",
-      "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==",
-      "dev": true
-    },
-    "node_modules/has": {
-      "version": "1.0.3",
-      "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
-      "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
-      "dev": true,
-      "dependencies": {
-        "function-bind": "^1.1.1"
-      },
-      "engines": {
-        "node": ">= 0.4.0"
-      }
-    },
-    "node_modules/has-bigints": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz",
-      "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==",
-      "dev": true,
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/has-flag": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
-      "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
-      "dev": true,
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/has-property-descriptors": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz",
-      "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==",
-      "dev": true,
-      "dependencies": {
-        "get-intrinsic": "^1.1.1"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/has-proto": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz",
-      "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==",
-      "dev": true,
-      "engines": {
-        "node": ">= 0.4"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/has-symbols": {
-      "version": "1.0.3",
-      "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
-      "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
-      "dev": true,
-      "engines": {
-        "node": ">= 0.4"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/has-tostringtag": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz",
-      "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==",
-      "dev": true,
-      "dependencies": {
-        "has-symbols": "^1.0.2"
-      },
-      "engines": {
-        "node": ">= 0.4"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/ignore": {
-      "version": "5.2.4",
-      "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",
-      "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==",
-      "dev": true,
-      "engines": {
-        "node": ">= 4"
-      }
-    },
-    "node_modules/import-fresh": {
-      "version": "3.3.0",
-      "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
-      "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
-      "dev": true,
-      "dependencies": {
-        "parent-module": "^1.0.0",
-        "resolve-from": "^4.0.0"
-      },
-      "engines": {
-        "node": ">=6"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
-      }
-    },
-    "node_modules/imurmurhash": {
-      "version": "0.1.4",
-      "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
-      "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
-      "dev": true,
-      "engines": {
-        "node": ">=0.8.19"
-      }
-    },
-    "node_modules/inflight": {
-      "version": "1.0.6",
-      "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
-      "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
-      "dev": true,
-      "dependencies": {
-        "once": "^1.3.0",
-        "wrappy": "1"
-      }
-    },
-    "node_modules/inherits": {
-      "version": "2.0.4",
-      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
-      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
-      "dev": true
-    },
-    "node_modules/internal-slot": {
-      "version": "1.0.5",
-      "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz",
-      "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==",
-      "dev": true,
-      "dependencies": {
-        "get-intrinsic": "^1.2.0",
-        "has": "^1.0.3",
-        "side-channel": "^1.0.4"
-      },
-      "engines": {
-        "node": ">= 0.4"
-      }
-    },
-    "node_modules/is-array-buffer": {
-      "version": "3.0.2",
-      "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz",
-      "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==",
-      "dev": true,
-      "dependencies": {
-        "call-bind": "^1.0.2",
-        "get-intrinsic": "^1.2.0",
-        "is-typed-array": "^1.1.10"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/is-bigint": {
-      "version": "1.0.4",
-      "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz",
-      "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==",
-      "dev": true,
-      "dependencies": {
-        "has-bigints": "^1.0.1"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/is-boolean-object": {
-      "version": "1.1.2",
-      "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz",
-      "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==",
-      "dev": true,
-      "dependencies": {
-        "call-bind": "^1.0.2",
-        "has-tostringtag": "^1.0.0"
-      },
-      "engines": {
-        "node": ">= 0.4"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/is-callable": {
-      "version": "1.2.7",
-      "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
-      "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==",
-      "dev": true,
-      "engines": {
-        "node": ">= 0.4"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/is-core-module": {
-      "version": "2.12.0",
-      "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.0.tgz",
-      "integrity": "sha512-RECHCBCd/viahWmwj6enj19sKbHfJrddi/6cBDsNTKbNq0f7VeaUkBo60BqzvPqo/W54ChS62Z5qyun7cfOMqQ==",
-      "dev": true,
-      "dependencies": {
-        "has": "^1.0.3"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/is-date-object": {
-      "version": "1.0.5",
-      "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz",
-      "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==",
-      "dev": true,
-      "dependencies": {
-        "has-tostringtag": "^1.0.0"
-      },
-      "engines": {
-        "node": ">= 0.4"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/is-extglob": {
-      "version": "2.1.1",
-      "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
-      "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
-      "dev": true,
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
-    "node_modules/is-glob": {
-      "version": "4.0.3",
-      "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
-      "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
-      "dev": true,
-      "dependencies": {
-        "is-extglob": "^2.1.1"
-      },
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
-    "node_modules/is-negative-zero": {
-      "version": "2.0.2",
-      "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz",
-      "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==",
-      "dev": true,
-      "engines": {
-        "node": ">= 0.4"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/is-number": {
-      "version": "7.0.0",
-      "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
-      "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
-      "dev": true,
-      "engines": {
-        "node": ">=0.12.0"
-      }
-    },
-    "node_modules/is-number-object": {
-      "version": "1.0.7",
-      "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz",
-      "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==",
-      "dev": true,
-      "dependencies": {
-        "has-tostringtag": "^1.0.0"
-      },
-      "engines": {
-        "node": ">= 0.4"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/is-path-inside": {
-      "version": "3.0.3",
-      "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
-      "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
-      "dev": true,
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/is-regex": {
-      "version": "1.1.4",
-      "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz",
-      "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==",
-      "dev": true,
-      "dependencies": {
-        "call-bind": "^1.0.2",
-        "has-tostringtag": "^1.0.0"
-      },
-      "engines": {
-        "node": ">= 0.4"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/is-shared-array-buffer": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz",
-      "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==",
-      "dev": true,
-      "dependencies": {
-        "call-bind": "^1.0.2"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/is-string": {
-      "version": "1.0.7",
-      "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz",
-      "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==",
-      "dev": true,
-      "dependencies": {
-        "has-tostringtag": "^1.0.0"
-      },
-      "engines": {
-        "node": ">= 0.4"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/is-symbol": {
-      "version": "1.0.4",
-      "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz",
-      "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==",
-      "dev": true,
-      "dependencies": {
-        "has-symbols": "^1.0.2"
-      },
-      "engines": {
-        "node": ">= 0.4"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/is-typed-array": {
-      "version": "1.1.10",
-      "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz",
-      "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==",
-      "dev": true,
-      "dependencies": {
-        "available-typed-arrays": "^1.0.5",
-        "call-bind": "^1.0.2",
-        "for-each": "^0.3.3",
-        "gopd": "^1.0.1",
-        "has-tostringtag": "^1.0.0"
-      },
-      "engines": {
-        "node": ">= 0.4"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/is-weakref": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz",
-      "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==",
-      "dev": true,
-      "dependencies": {
-        "call-bind": "^1.0.2"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/isexe": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
-      "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
-      "dev": true
-    },
-    "node_modules/js-sdsl": {
-      "version": "4.4.0",
-      "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.0.tgz",
-      "integrity": "sha512-FfVSdx6pJ41Oa+CF7RDaFmTnCaFhua+SNYQX74riGOpl96x+2jQCqEfQ2bnXu/5DPCqlRuiqyvTJM0Qjz26IVg==",
-      "dev": true,
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/js-sdsl"
-      }
-    },
-    "node_modules/js-tokens": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
-      "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
-    },
-    "node_modules/js-yaml": {
-      "version": "4.1.0",
-      "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
-      "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
-      "dev": true,
-      "dependencies": {
-        "argparse": "^2.0.1"
-      },
-      "bin": {
-        "js-yaml": "bin/js-yaml.js"
-      }
-    },
-    "node_modules/json-pointer": {
-      "version": "0.6.2",
-      "resolved": "https://registry.npmjs.org/json-pointer/-/json-pointer-0.6.2.tgz",
-      "integrity": "sha512-vLWcKbOaXlO+jvRy4qNd+TI1QUPZzfJj1tpJ3vAXDych5XJf93ftpUKe5pKCrzyIIwgBJcOcCVRUfqQP25afBw==",
-      "dependencies": {
-        "foreach": "^2.0.4"
-      }
-    },
-    "node_modules/json-schema-traverse": {
-      "version": "0.4.1",
-      "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
-      "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
-      "dev": true
-    },
-    "node_modules/json-stable-stringify-without-jsonify": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
-      "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
-      "dev": true
-    },
-    "node_modules/jsx-ast-utils": {
-      "version": "3.3.3",
-      "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.3.tgz",
-      "integrity": "sha512-fYQHZTZ8jSfmWZ0iyzfwiU4WDX4HpHbMCZ3gPlWYiCl3BoeOTsqKBqnTVfH2rYT7eP5c3sVbeSPHnnJOaTrWiw==",
-      "dev": true,
-      "dependencies": {
-        "array-includes": "^3.1.5",
-        "object.assign": "^4.1.3"
-      },
-      "engines": {
-        "node": ">=4.0"
-      }
-    },
-    "node_modules/levn": {
-      "version": "0.4.1",
-      "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
-      "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
-      "dev": true,
-      "dependencies": {
-        "prelude-ls": "^1.2.1",
-        "type-check": "~0.4.0"
-      },
-      "engines": {
-        "node": ">= 0.8.0"
-      }
-    },
-    "node_modules/locate-path": {
-      "version": "6.0.0",
-      "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
-      "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
-      "dev": true,
-      "dependencies": {
-        "p-locate": "^5.0.0"
-      },
-      "engines": {
-        "node": ">=10"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
-      }
-    },
-    "node_modules/lodash.merge": {
-      "version": "4.6.2",
-      "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
-      "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
-      "dev": true
-    },
-    "node_modules/loose-envify": {
-      "version": "1.4.0",
-      "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
-      "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
-      "dependencies": {
-        "js-tokens": "^3.0.0 || ^4.0.0"
-      },
-      "bin": {
-        "loose-envify": "cli.js"
-      }
-    },
-    "node_modules/lru-cache": {
-      "version": "6.0.0",
-      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
-      "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
-      "dev": true,
-      "dependencies": {
-        "yallist": "^4.0.0"
-      },
-      "engines": {
-        "node": ">=10"
-      }
-    },
-    "node_modules/merge2": {
-      "version": "1.4.1",
-      "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
-      "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
-      "dev": true,
-      "engines": {
-        "node": ">= 8"
-      }
-    },
-    "node_modules/micromatch": {
-      "version": "4.0.5",
-      "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
-      "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
-      "dev": true,
-      "dependencies": {
-        "braces": "^3.0.2",
-        "picomatch": "^2.3.1"
-      },
-      "engines": {
-        "node": ">=8.6"
-      }
-    },
-    "node_modules/minimatch": {
-      "version": "3.1.2",
-      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
-      "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
-      "dev": true,
-      "dependencies": {
-        "brace-expansion": "^1.1.7"
-      },
-      "engines": {
-        "node": "*"
-      }
-    },
-    "node_modules/ms": {
-      "version": "2.1.2",
-      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
-      "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
-      "dev": true
-    },
-    "node_modules/nanoid": {
-      "version": "3.3.6",
-      "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
-      "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==",
-      "dev": true,
-      "funding": [
-        {
-          "type": "github",
-          "url": "https://github.com/sponsors/ai"
-        }
-      ],
-      "bin": {
-        "nanoid": "bin/nanoid.cjs"
-      },
-      "engines": {
-        "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
-      }
-    },
-    "node_modules/natural-compare": {
-      "version": "1.4.0",
-      "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
-      "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
-      "dev": true
-    },
-    "node_modules/natural-compare-lite": {
-      "version": "1.4.0",
-      "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz",
-      "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==",
-      "dev": true
-    },
-    "node_modules/object-assign": {
-      "version": "4.1.1",
-      "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
-      "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
-    "node_modules/object-inspect": {
-      "version": "1.12.3",
-      "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz",
-      "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==",
-      "dev": true,
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/object-keys": {
-      "version": "1.1.1",
-      "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
-      "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
-      "dev": true,
-      "engines": {
-        "node": ">= 0.4"
-      }
-    },
-    "node_modules/object.assign": {
-      "version": "4.1.4",
-      "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz",
-      "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==",
-      "dev": true,
-      "dependencies": {
-        "call-bind": "^1.0.2",
-        "define-properties": "^1.1.4",
-        "has-symbols": "^1.0.3",
-        "object-keys": "^1.1.1"
-      },
-      "engines": {
-        "node": ">= 0.4"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/object.entries": {
-      "version": "1.1.6",
-      "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.6.tgz",
-      "integrity": "sha512-leTPzo4Zvg3pmbQ3rDK69Rl8GQvIqMWubrkxONG9/ojtFE2rD9fjMKfSI5BxW3osRH1m6VdzmqK8oAY9aT4x5w==",
-      "dev": true,
-      "dependencies": {
-        "call-bind": "^1.0.2",
-        "define-properties": "^1.1.4",
-        "es-abstract": "^1.20.4"
-      },
-      "engines": {
-        "node": ">= 0.4"
-      }
-    },
-    "node_modules/object.fromentries": {
-      "version": "2.0.6",
-      "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.6.tgz",
-      "integrity": "sha512-VciD13dswC4j1Xt5394WR4MzmAQmlgN72phd/riNp9vtD7tp4QQWJ0R4wvclXcafgcYK8veHRed2W6XeGBvcfg==",
-      "dev": true,
-      "dependencies": {
-        "call-bind": "^1.0.2",
-        "define-properties": "^1.1.4",
-        "es-abstract": "^1.20.4"
-      },
-      "engines": {
-        "node": ">= 0.4"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/object.hasown": {
-      "version": "1.1.2",
-      "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.2.tgz",
-      "integrity": "sha512-B5UIT3J1W+WuWIU55h0mjlwaqxiE5vYENJXIXZ4VFe05pNYrkKuK0U/6aFcb0pKywYJh7IhfoqUfKVmrJJHZHw==",
-      "dev": true,
-      "dependencies": {
-        "define-properties": "^1.1.4",
-        "es-abstract": "^1.20.4"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/object.values": {
-      "version": "1.1.6",
-      "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz",
-      "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==",
-      "dev": true,
-      "dependencies": {
-        "call-bind": "^1.0.2",
-        "define-properties": "^1.1.4",
-        "es-abstract": "^1.20.4"
-      },
-      "engines": {
-        "node": ">= 0.4"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/once": {
-      "version": "1.4.0",
-      "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
-      "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
-      "dev": true,
-      "dependencies": {
-        "wrappy": "1"
-      }
-    },
-    "node_modules/optionator": {
-      "version": "0.9.1",
-      "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",
-      "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==",
-      "dev": true,
-      "dependencies": {
-        "deep-is": "^0.1.3",
-        "fast-levenshtein": "^2.0.6",
-        "levn": "^0.4.1",
-        "prelude-ls": "^1.2.1",
-        "type-check": "^0.4.0",
-        "word-wrap": "^1.2.3"
-      },
-      "engines": {
-        "node": ">= 0.8.0"
-      }
-    },
-    "node_modules/p-limit": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
-      "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
-      "dev": true,
-      "dependencies": {
-        "yocto-queue": "^0.1.0"
-      },
-      "engines": {
-        "node": ">=10"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
-      }
-    },
-    "node_modules/p-locate": {
-      "version": "5.0.0",
-      "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
-      "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
-      "dev": true,
-      "dependencies": {
-        "p-limit": "^3.0.2"
-      },
-      "engines": {
-        "node": ">=10"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
+        "foreach": "^2.0.4"
       }
     },
-    "node_modules/parent-module": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
-      "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
-      "dev": true,
+    "node_modules/loose-envify": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+      "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+      "peer": true,
       "dependencies": {
-        "callsites": "^3.0.0"
+        "js-tokens": "^3.0.0 || ^4.0.0"
       },
-      "engines": {
-        "node": ">=6"
+      "bin": {
+        "loose-envify": "cli.js"
       }
     },
-    "node_modules/path-exists": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
-      "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+    "node_modules/nanoid": {
+      "version": "3.3.6",
+      "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
+      "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==",
       "dev": true,
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "bin": {
+        "nanoid": "bin/nanoid.cjs"
+      },
       "engines": {
-        "node": ">=8"
+        "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
       }
     },
-    "node_modules/path-is-absolute": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
-      "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
-      "dev": true,
+    "node_modules/object-assign": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+      "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+      "peer": true,
       "engines": {
         "node": ">=0.10.0"
       }
     },
-    "node_modules/path-key": {
-      "version": "3.1.1",
-      "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
-      "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
-      "dev": true,
-      "engines": {
-        "node": ">=8"
-      }
-    },
     "node_modules/path-parse": {
       "version": "1.0.7",
       "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
       "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
       "dev": true
     },
-    "node_modules/path-type": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
-      "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
-      "dev": true,
-      "engines": {
-        "node": ">=8"
-      }
-    },
     "node_modules/picocolors": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
       "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
       "dev": true
     },
-    "node_modules/picomatch": {
-      "version": "2.3.1",
-      "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
-      "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
-      "dev": true,
-      "engines": {
-        "node": ">=8.6"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/jonschlinkert"
-      }
-    },
     "node_modules/postcss": {
       "version": "8.4.21",
       "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz",
@@ -2637,70 +611,6 @@
         "url": "https://opencollective.com/preact"
       }
     },
-    "node_modules/prelude-ls": {
-      "version": "1.2.1",
-      "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
-      "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
-      "dev": true,
-      "engines": {
-        "node": ">= 0.8.0"
-      }
-    },
-    "node_modules/prettier": {
-      "version": "3.0.0-alpha.6",
-      "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.0-alpha.6.tgz",
-      "integrity": "sha512-AdbQSZ6Oo+iy9Ekzmsgno05P1uX2vqPkjOMJqRfP8hTe+m6iDw4Nt7bPFpWZ/HYCU+3f0P5U0o2ghxQwwkLH7A==",
-      "dev": true,
-      "bin": {
-        "prettier": "bin/prettier.cjs"
-      },
-      "engines": {
-        "node": ">=14"
-      },
-      "funding": {
-        "url": "https://github.com/prettier/prettier?sponsor=1"
-      }
-    },
-    "node_modules/prop-types": {
-      "version": "15.8.1",
-      "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
-      "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
-      "dev": true,
-      "dependencies": {
-        "loose-envify": "^1.4.0",
-        "object-assign": "^4.1.1",
-        "react-is": "^16.13.1"
-      }
-    },
-    "node_modules/punycode": {
-      "version": "2.3.0",
-      "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz",
-      "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==",
-      "dev": true,
-      "engines": {
-        "node": ">=6"
-      }
-    },
-    "node_modules/queue-microtask": {
-      "version": "1.2.3",
-      "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
-      "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
-      "dev": true,
-      "funding": [
-        {
-          "type": "github",
-          "url": "https://github.com/sponsors/feross"
-        },
-        {
-          "type": "patreon",
-          "url": "https://www.patreon.com/feross"
-        },
-        {
-          "type": "consulting",
-          "url": "https://feross.org/support"
-        }
-      ]
-    },
     "node_modules/react": {
       "version": "17.0.2",
       "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz",
@@ -2728,29 +638,6 @@
         "react": "17.0.2"
       }
     },
-    "node_modules/react-is": {
-      "version": "16.13.1",
-      "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
-      "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
-      "dev": true
-    },
-    "node_modules/regexp.prototype.flags": {
-      "version": "1.4.3",
-      "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz",
-      "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==",
-      "dev": true,
-      "dependencies": {
-        "call-bind": "^1.0.2",
-        "define-properties": "^1.1.3",
-        "functions-have-names": "^1.2.2"
-      },
-      "engines": {
-        "node": ">= 0.4"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
     "node_modules/resolve": {
       "version": "1.22.2",
       "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz",
@@ -2768,40 +655,6 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
-    "node_modules/resolve-from": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
-      "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
-      "dev": true,
-      "engines": {
-        "node": ">=4"
-      }
-    },
-    "node_modules/reusify": {
-      "version": "1.0.4",
-      "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
-      "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
-      "dev": true,
-      "engines": {
-        "iojs": ">=1.0.0",
-        "node": ">=0.10.0"
-      }
-    },
-    "node_modules/rimraf": {
-      "version": "3.0.2",
-      "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
-      "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
-      "dev": true,
-      "dependencies": {
-        "glob": "^7.1.3"
-      },
-      "bin": {
-        "rimraf": "bin.js"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/isaacs"
-      }
-    },
     "node_modules/rollup": {
       "version": "2.79.1",
       "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz",
@@ -2817,43 +670,6 @@
         "fsevents": "~2.3.2"
       }
     },
-    "node_modules/run-parallel": {
-      "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
-      "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
-      "dev": true,
-      "funding": [
-        {
-          "type": "github",
-          "url": "https://github.com/sponsors/feross"
-        },
-        {
-          "type": "patreon",
-          "url": "https://www.patreon.com/feross"
-        },
-        {
-          "type": "consulting",
-          "url": "https://feross.org/support"
-        }
-      ],
-      "dependencies": {
-        "queue-microtask": "^1.2.2"
-      }
-    },
-    "node_modules/safe-regex-test": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz",
-      "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==",
-      "dev": true,
-      "dependencies": {
-        "call-bind": "^1.0.2",
-        "get-intrinsic": "^1.1.3",
-        "is-regex": "^1.1.4"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
     "node_modules/scheduler": {
       "version": "0.20.2",
       "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz",
@@ -2864,65 +680,6 @@
         "object-assign": "^4.1.1"
       }
     },
-    "node_modules/semver": {
-      "version": "7.4.0",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-7.4.0.tgz",
-      "integrity": "sha512-RgOxM8Mw+7Zus0+zcLEUn8+JfoLpj/huFTItQy2hsM4khuC1HYRDp0cU482Ewn/Fcy6bCjufD8vAj7voC66KQw==",
-      "dev": true,
-      "dependencies": {
-        "lru-cache": "^6.0.0"
-      },
-      "bin": {
-        "semver": "bin/semver.js"
-      },
-      "engines": {
-        "node": ">=10"
-      }
-    },
-    "node_modules/shebang-command": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
-      "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
-      "dev": true,
-      "dependencies": {
-        "shebang-regex": "^3.0.0"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/shebang-regex": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
-      "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
-      "dev": true,
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/side-channel": {
-      "version": "1.0.4",
-      "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
-      "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
-      "dev": true,
-      "dependencies": {
-        "call-bind": "^1.0.0",
-        "get-intrinsic": "^1.0.2",
-        "object-inspect": "^1.9.0"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/slash": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
-      "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
-      "dev": true,
-      "engines": {
-        "node": ">=8"
-      }
-    },
     "node_modules/source-map-js": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
@@ -2932,106 +689,6 @@
         "node": ">=0.10.0"
       }
     },
-    "node_modules/string.prototype.matchall": {
-      "version": "4.0.8",
-      "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.8.tgz",
-      "integrity": "sha512-6zOCOcJ+RJAQshcTvXPHoxoQGONa3e/Lqx90wUA+wEzX78sg5Bo+1tQo4N0pohS0erG9qtCqJDjNCQBjeWVxyg==",
-      "dev": true,
-      "dependencies": {
-        "call-bind": "^1.0.2",
-        "define-properties": "^1.1.4",
-        "es-abstract": "^1.20.4",
-        "get-intrinsic": "^1.1.3",
-        "has-symbols": "^1.0.3",
-        "internal-slot": "^1.0.3",
-        "regexp.prototype.flags": "^1.4.3",
-        "side-channel": "^1.0.4"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/string.prototype.trim": {
-      "version": "1.2.7",
-      "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz",
-      "integrity": "sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==",
-      "dev": true,
-      "dependencies": {
-        "call-bind": "^1.0.2",
-        "define-properties": "^1.1.4",
-        "es-abstract": "^1.20.4"
-      },
-      "engines": {
-        "node": ">= 0.4"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/string.prototype.trimend": {
-      "version": "1.0.6",
-      "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz",
-      "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==",
-      "dev": true,
-      "dependencies": {
-        "call-bind": "^1.0.2",
-        "define-properties": "^1.1.4",
-        "es-abstract": "^1.20.4"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/string.prototype.trimstart": {
-      "version": "1.0.6",
-      "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz",
-      "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==",
-      "dev": true,
-      "dependencies": {
-        "call-bind": "^1.0.2",
-        "define-properties": "^1.1.4",
-        "es-abstract": "^1.20.4"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/strip-ansi": {
-      "version": "6.0.1",
-      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
-      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
-      "dev": true,
-      "dependencies": {
-        "ansi-regex": "^5.0.1"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/strip-json-comments": {
-      "version": "3.1.1",
-      "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
-      "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
-      "dev": true,
-      "engines": {
-        "node": ">=8"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
-      }
-    },
-    "node_modules/supports-color": {
-      "version": "7.2.0",
-      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
-      "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
-      "dev": true,
-      "dependencies": {
-        "has-flag": "^4.0.0"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
     "node_modules/supports-preserve-symlinks-flag": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
@@ -3044,109 +701,23 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
-    "node_modules/text-table": {
-      "version": "0.2.0",
-      "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
-      "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
-      "dev": true
-    },
-    "node_modules/to-regex-range": {
-      "version": "5.0.1",
-      "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
-      "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
-      "dev": true,
-      "dependencies": {
-        "is-number": "^7.0.0"
-      },
-      "engines": {
-        "node": ">=8.0"
-      }
-    },
-    "node_modules/tslib": {
-      "version": "1.14.1",
-      "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
-      "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
-      "dev": true
-    },
-    "node_modules/type-check": {
-      "version": "0.4.0",
-      "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
-      "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
-      "dev": true,
-      "dependencies": {
-        "prelude-ls": "^1.2.1"
-      },
-      "engines": {
-        "node": ">= 0.8.0"
-      }
-    },
-    "node_modules/type-fest": {
-      "version": "0.20.2",
-      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
-      "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
-      "dev": true,
-      "engines": {
-        "node": ">=10"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
-      }
-    },
-    "node_modules/typed-array-length": {
-      "version": "1.0.4",
-      "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz",
-      "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==",
-      "dev": true,
-      "dependencies": {
-        "call-bind": "^1.0.2",
-        "for-each": "^0.3.3",
-        "is-typed-array": "^1.1.9"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
     "node_modules/typescript": {
       "version": "4.9.5",
-      "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
-      "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
-      "dev": true,
-      "bin": {
-        "tsc": "bin/tsc",
-        "tsserver": "bin/tsserver"
-      },
-      "engines": {
-        "node": ">=4.2.0"
-      }
-    },
-    "node_modules/unbox-primitive": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz",
-      "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==",
-      "dev": true,
-      "dependencies": {
-        "call-bind": "^1.0.2",
-        "has-bigints": "^1.0.2",
-        "has-symbols": "^1.0.3",
-        "which-boxed-primitive": "^1.0.2"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/uri-js": {
-      "version": "4.4.1",
-      "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
-      "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+      "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
+      "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
       "dev": true,
-      "dependencies": {
-        "punycode": "^2.1.0"
+      "bin": {
+        "tsc": "bin/tsc",
+        "tsserver": "bin/tsserver"
+      },
+      "engines": {
+        "node": ">=4.2.0"
       }
     },
     "node_modules/vite": {
-      "version": "3.2.5",
-      "resolved": "https://registry.npmjs.org/vite/-/vite-3.2.5.tgz",
-      "integrity": "sha512-4mVEpXpSOgrssFZAOmGIr85wPHKvaDAcXqxVxVRZhljkJOMZi1ibLibzjLHzJvcok8BMguLc7g1W6W/GqZbLdQ==",
+      "version": "3.2.7",
+      "resolved": "https://registry.npmjs.org/vite/-/vite-3.2.7.tgz",
+      "integrity": "sha512-29pdXjk49xAP0QBr0xXqu2s5jiQIXNvE/xwd0vUizYT2Hzqe4BksNNoWllFVXJf4eLZ+UlVQmXfB4lWrc+t18g==",
       "dev": true,
       "dependencies": {
         "esbuild": "^0.15.9",
@@ -3191,90 +762,6 @@
           "optional": true
         }
       }
-    },
-    "node_modules/which": {
-      "version": "2.0.2",
-      "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
-      "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
-      "dev": true,
-      "dependencies": {
-        "isexe": "^2.0.0"
-      },
-      "bin": {
-        "node-which": "bin/node-which"
-      },
-      "engines": {
-        "node": ">= 8"
-      }
-    },
-    "node_modules/which-boxed-primitive": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz",
-      "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==",
-      "dev": true,
-      "dependencies": {
-        "is-bigint": "^1.0.1",
-        "is-boolean-object": "^1.1.0",
-        "is-number-object": "^1.0.4",
-        "is-string": "^1.0.5",
-        "is-symbol": "^1.0.3"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/which-typed-array": {
-      "version": "1.1.9",
-      "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz",
-      "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==",
-      "dev": true,
-      "dependencies": {
-        "available-typed-arrays": "^1.0.5",
-        "call-bind": "^1.0.2",
-        "for-each": "^0.3.3",
-        "gopd": "^1.0.1",
-        "has-tostringtag": "^1.0.0",
-        "is-typed-array": "^1.1.10"
-      },
-      "engines": {
-        "node": ">= 0.4"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/word-wrap": {
-      "version": "1.2.3",
-      "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
-      "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
-      "dev": true,
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
-    "node_modules/wrappy": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
-      "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
-      "dev": true
-    },
-    "node_modules/yallist": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
-      "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
-      "dev": true
-    },
-    "node_modules/yocto-queue": {
-      "version": "0.1.0",
-      "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
-      "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
-      "dev": true,
-      "engines": {
-        "node": ">=10"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
-      }
     }
   },
   "dependencies": {
@@ -3292,93 +779,6 @@
       "dev": true,
       "optional": true
     },
-    "@eslint-community/eslint-utils": {
-      "version": "4.4.0",
-      "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
-      "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==",
-      "dev": true,
-      "requires": {
-        "eslint-visitor-keys": "^3.3.0"
-      }
-    },
-    "@eslint-community/regexpp": {
-      "version": "4.5.0",
-      "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.5.0.tgz",
-      "integrity": "sha512-vITaYzIcNmjn5tF5uxcZ/ft7/RXGrMUIS9HalWckEOF6ESiwXKoMzAQf2UW0aVd6rnOeExTJVd5hmWXucBKGXQ==",
-      "dev": true
-    },
-    "@eslint/eslintrc": {
-      "version": "2.0.2",
-      "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.2.tgz",
-      "integrity": "sha512-3W4f5tDUra+pA+FzgugqL2pRimUTDJWKr7BINqOpkZrC0uYI0NIc0/JFgBROCU07HR6GieA5m3/rsPIhDmCXTQ==",
-      "dev": true,
-      "requires": {
-        "ajv": "^6.12.4",
-        "debug": "^4.3.2",
-        "espree": "^9.5.1",
-        "globals": "^13.19.0",
-        "ignore": "^5.2.0",
-        "import-fresh": "^3.2.1",
-        "js-yaml": "^4.1.0",
-        "minimatch": "^3.1.2",
-        "strip-json-comments": "^3.1.1"
-      }
-    },
-    "@eslint/js": {
-      "version": "8.38.0",
-      "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.38.0.tgz",
-      "integrity": "sha512-IoD2MfUnOV58ghIHCiil01PcohxjbYR/qCxsoC+xNgUwh1EY8jOOrYmu3d3a71+tJJ23uscEV4X2HJWMsPJu4g==",
-      "dev": true
-    },
-    "@humanwhocodes/config-array": {
-      "version": "0.11.8",
-      "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz",
-      "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==",
-      "dev": true,
-      "requires": {
-        "@humanwhocodes/object-schema": "^1.2.1",
-        "debug": "^4.1.1",
-        "minimatch": "^3.0.5"
-      }
-    },
-    "@humanwhocodes/module-importer": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
-      "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
-      "dev": true
-    },
-    "@humanwhocodes/object-schema": {
-      "version": "1.2.1",
-      "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz",
-      "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
-      "dev": true
-    },
-    "@nodelib/fs.scandir": {
-      "version": "2.1.5",
-      "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
-      "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
-      "dev": true,
-      "requires": {
-        "@nodelib/fs.stat": "2.0.5",
-        "run-parallel": "^1.1.9"
-      }
-    },
-    "@nodelib/fs.stat": {
-      "version": "2.0.5",
-      "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
-      "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
-      "dev": true
-    },
-    "@nodelib/fs.walk": {
-      "version": "1.2.8",
-      "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
-      "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
-      "dev": true,
-      "requires": {
-        "@nodelib/fs.scandir": "2.1.5",
-        "fastq": "^1.6.0"
-      }
-    },
     "@reactpy/client": {
       "version": "0.2.1",
       "resolved": "https://registry.npmjs.org/@reactpy/client/-/client-0.2.1.tgz",
@@ -3388,12 +788,6 @@
         "json-pointer": "^0.6.2"
       }
     },
-    "@types/json-schema": {
-      "version": "7.0.11",
-      "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
-      "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
-      "dev": true
-    },
     "@types/prop-types": {
       "version": "15.7.5",
       "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz",
@@ -3408,491 +802,30 @@
       "requires": {
         "@types/prop-types": "*",
         "@types/scheduler": "*",
-        "csstype": "^3.0.2"
-      }
-    },
-    "@types/react-dom": {
-      "version": "17.0.19",
-      "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.19.tgz",
-      "integrity": "sha512-PiYG40pnQRdPHnlf7tZnp0aQ6q9tspYr72vD61saO6zFCybLfMqwUCN0va1/P+86DXn18ZWeW30Bk7xlC5eEAQ==",
-      "dev": true,
-      "requires": {
-        "@types/react": "^17"
-      }
-    },
-    "@types/scheduler": {
-      "version": "0.16.3",
-      "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz",
-      "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==",
-      "dev": true
-    },
-    "@types/semver": {
-      "version": "7.3.13",
-      "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz",
-      "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==",
-      "dev": true
-    },
-    "@typescript-eslint/eslint-plugin": {
-      "version": "5.58.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.58.0.tgz",
-      "integrity": "sha512-vxHvLhH0qgBd3/tW6/VccptSfc8FxPQIkmNTVLWcCOVqSBvqpnKkBTYrhcGlXfSnd78azwe+PsjYFj0X34/njA==",
-      "dev": true,
-      "requires": {
-        "@eslint-community/regexpp": "^4.4.0",
-        "@typescript-eslint/scope-manager": "5.58.0",
-        "@typescript-eslint/type-utils": "5.58.0",
-        "@typescript-eslint/utils": "5.58.0",
-        "debug": "^4.3.4",
-        "grapheme-splitter": "^1.0.4",
-        "ignore": "^5.2.0",
-        "natural-compare-lite": "^1.4.0",
-        "semver": "^7.3.7",
-        "tsutils": "^3.21.0"
-      },
-      "dependencies": {
-        "tsutils": {
-          "version": "3.21.0",
-          "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz",
-          "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==",
-          "dev": true,
-          "requires": {
-            "tslib": "^1.8.1"
-          }
-        }
-      }
-    },
-    "@typescript-eslint/parser": {
-      "version": "5.58.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.58.0.tgz",
-      "integrity": "sha512-ixaM3gRtlfrKzP8N6lRhBbjTow1t6ztfBvQNGuRM8qH1bjFFXIJ35XY+FC0RRBKn3C6cT+7VW1y8tNm7DwPHDQ==",
-      "dev": true,
-      "requires": {
-        "@typescript-eslint/scope-manager": "5.58.0",
-        "@typescript-eslint/types": "5.58.0",
-        "@typescript-eslint/typescript-estree": "5.58.0",
-        "debug": "^4.3.4"
-      }
-    },
-    "@typescript-eslint/scope-manager": {
-      "version": "5.58.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.58.0.tgz",
-      "integrity": "sha512-b+w8ypN5CFvrXWQb9Ow9T4/6LC2MikNf1viLkYTiTbkQl46CnR69w7lajz1icW0TBsYmlpg+mRzFJ4LEJ8X9NA==",
-      "dev": true,
-      "requires": {
-        "@typescript-eslint/types": "5.58.0",
-        "@typescript-eslint/visitor-keys": "5.58.0"
-      }
-    },
-    "@typescript-eslint/type-utils": {
-      "version": "5.58.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.58.0.tgz",
-      "integrity": "sha512-FF5vP/SKAFJ+LmR9PENql7fQVVgGDOS+dq3j+cKl9iW/9VuZC/8CFmzIP0DLKXfWKpRHawJiG70rVH+xZZbp8w==",
-      "dev": true,
-      "requires": {
-        "@typescript-eslint/typescript-estree": "5.58.0",
-        "@typescript-eslint/utils": "5.58.0",
-        "debug": "^4.3.4",
-        "tsutils": "^3.21.0"
-      },
-      "dependencies": {
-        "tsutils": {
-          "version": "3.21.0",
-          "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz",
-          "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==",
-          "dev": true,
-          "requires": {
-            "tslib": "^1.8.1"
-          }
-        }
-      }
-    },
-    "@typescript-eslint/types": {
-      "version": "5.58.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.58.0.tgz",
-      "integrity": "sha512-JYV4eITHPzVQMnHZcYJXl2ZloC7thuUHrcUmxtzvItyKPvQ50kb9QXBkgNAt90OYMqwaodQh2kHutWZl1fc+1g==",
-      "dev": true
-    },
-    "@typescript-eslint/typescript-estree": {
-      "version": "5.58.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.58.0.tgz",
-      "integrity": "sha512-cRACvGTodA+UxnYM2uwA2KCwRL7VAzo45syNysqlMyNyjw0Z35Icc9ihPJZjIYuA5bXJYiJ2YGUB59BqlOZT1Q==",
-      "dev": true,
-      "requires": {
-        "@typescript-eslint/types": "5.58.0",
-        "@typescript-eslint/visitor-keys": "5.58.0",
-        "debug": "^4.3.4",
-        "globby": "^11.1.0",
-        "is-glob": "^4.0.3",
-        "semver": "^7.3.7",
-        "tsutils": "^3.21.0"
-      },
-      "dependencies": {
-        "tsutils": {
-          "version": "3.21.0",
-          "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz",
-          "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==",
-          "dev": true,
-          "requires": {
-            "tslib": "^1.8.1"
-          }
-        }
-      }
-    },
-    "@typescript-eslint/utils": {
-      "version": "5.58.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.58.0.tgz",
-      "integrity": "sha512-gAmLOTFXMXOC+zP1fsqm3VceKSBQJNzV385Ok3+yzlavNHZoedajjS4UyS21gabJYcobuigQPs/z71A9MdJFqQ==",
-      "dev": true,
-      "requires": {
-        "@eslint-community/eslint-utils": "^4.2.0",
-        "@types/json-schema": "^7.0.9",
-        "@types/semver": "^7.3.12",
-        "@typescript-eslint/scope-manager": "5.58.0",
-        "@typescript-eslint/types": "5.58.0",
-        "@typescript-eslint/typescript-estree": "5.58.0",
-        "eslint-scope": "^5.1.1",
-        "semver": "^7.3.7"
-      },
-      "dependencies": {
-        "eslint-scope": {
-          "version": "5.1.1",
-          "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
-          "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
-          "dev": true,
-          "requires": {
-            "esrecurse": "^4.3.0",
-            "estraverse": "^4.1.1"
-          }
-        },
-        "estraverse": {
-          "version": "4.3.0",
-          "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
-          "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
-          "dev": true
-        }
-      }
-    },
-    "@typescript-eslint/visitor-keys": {
-      "version": "5.58.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.58.0.tgz",
-      "integrity": "sha512-/fBraTlPj0jwdyTwLyrRTxv/3lnU2H96pNTVM6z3esTWLtA5MZ9ghSMJ7Rb+TtUAdtEw9EyJzJ0EydIMKxQ9gA==",
-      "dev": true,
-      "requires": {
-        "@typescript-eslint/types": "5.58.0",
-        "eslint-visitor-keys": "^3.3.0"
-      }
-    },
-    "acorn": {
-      "version": "8.8.2",
-      "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz",
-      "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==",
-      "dev": true
-    },
-    "acorn-jsx": {
-      "version": "5.3.2",
-      "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
-      "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
-      "dev": true,
-      "requires": {}
-    },
-    "ajv": {
-      "version": "6.12.6",
-      "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
-      "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
-      "dev": true,
-      "requires": {
-        "fast-deep-equal": "^3.1.1",
-        "fast-json-stable-stringify": "^2.0.0",
-        "json-schema-traverse": "^0.4.1",
-        "uri-js": "^4.2.2"
-      }
-    },
-    "ansi-regex": {
-      "version": "5.0.1",
-      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
-      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
-      "dev": true
-    },
-    "ansi-styles": {
-      "version": "4.3.0",
-      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
-      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
-      "dev": true,
-      "requires": {
-        "color-convert": "^2.0.1"
-      }
-    },
-    "argparse": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
-      "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
-      "dev": true
-    },
-    "array-buffer-byte-length": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz",
-      "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==",
-      "dev": true,
-      "requires": {
-        "call-bind": "^1.0.2",
-        "is-array-buffer": "^3.0.1"
-      }
-    },
-    "array-includes": {
-      "version": "3.1.6",
-      "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz",
-      "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==",
-      "dev": true,
-      "requires": {
-        "call-bind": "^1.0.2",
-        "define-properties": "^1.1.4",
-        "es-abstract": "^1.20.4",
-        "get-intrinsic": "^1.1.3",
-        "is-string": "^1.0.7"
-      }
-    },
-    "array-union": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
-      "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
-      "dev": true
-    },
-    "array.prototype.flatmap": {
-      "version": "1.3.1",
-      "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz",
-      "integrity": "sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==",
-      "dev": true,
-      "requires": {
-        "call-bind": "^1.0.2",
-        "define-properties": "^1.1.4",
-        "es-abstract": "^1.20.4",
-        "es-shim-unscopables": "^1.0.0"
-      }
-    },
-    "array.prototype.tosorted": {
-      "version": "1.1.1",
-      "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.1.tgz",
-      "integrity": "sha512-pZYPXPRl2PqWcsUs6LOMn+1f1532nEoPTYowBtqLwAW+W8vSVhkIGnmOX1t/UQjD6YGI0vcD2B1U7ZFGQH9jnQ==",
-      "dev": true,
-      "requires": {
-        "call-bind": "^1.0.2",
-        "define-properties": "^1.1.4",
-        "es-abstract": "^1.20.4",
-        "es-shim-unscopables": "^1.0.0",
-        "get-intrinsic": "^1.1.3"
-      }
-    },
-    "available-typed-arrays": {
-      "version": "1.0.5",
-      "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz",
-      "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==",
-      "dev": true
-    },
-    "balanced-match": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
-      "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
-      "dev": true
-    },
-    "brace-expansion": {
-      "version": "1.1.11",
-      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
-      "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
-      "dev": true,
-      "requires": {
-        "balanced-match": "^1.0.0",
-        "concat-map": "0.0.1"
-      }
-    },
-    "braces": {
-      "version": "3.0.2",
-      "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
-      "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
-      "dev": true,
-      "requires": {
-        "fill-range": "^7.0.1"
-      }
-    },
-    "call-bind": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
-      "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
-      "dev": true,
-      "requires": {
-        "function-bind": "^1.1.1",
-        "get-intrinsic": "^1.0.2"
-      }
-    },
-    "callsites": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
-      "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
-      "dev": true
-    },
-    "chalk": {
-      "version": "4.1.2",
-      "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
-      "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
-      "dev": true,
-      "requires": {
-        "ansi-styles": "^4.1.0",
-        "supports-color": "^7.1.0"
-      }
-    },
-    "color-convert": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
-      "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
-      "dev": true,
-      "requires": {
-        "color-name": "~1.1.4"
-      }
-    },
-    "color-name": {
-      "version": "1.1.4",
-      "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
-      "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
-      "dev": true
-    },
-    "concat-map": {
-      "version": "0.0.1",
-      "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
-      "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
-      "dev": true
-    },
-    "cross-spawn": {
-      "version": "7.0.3",
-      "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
-      "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
-      "dev": true,
-      "requires": {
-        "path-key": "^3.1.0",
-        "shebang-command": "^2.0.0",
-        "which": "^2.0.1"
-      }
-    },
-    "csstype": {
-      "version": "3.1.2",
-      "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz",
-      "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==",
-      "dev": true
-    },
-    "debug": {
-      "version": "4.3.4",
-      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
-      "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
-      "dev": true,
-      "requires": {
-        "ms": "2.1.2"
-      }
-    },
-    "deep-is": {
-      "version": "0.1.4",
-      "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
-      "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
-      "dev": true
-    },
-    "define-properties": {
-      "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz",
-      "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==",
-      "dev": true,
-      "requires": {
-        "has-property-descriptors": "^1.0.0",
-        "object-keys": "^1.1.1"
-      }
-    },
-    "dir-glob": {
-      "version": "3.0.1",
-      "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
-      "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
-      "dev": true,
-      "requires": {
-        "path-type": "^4.0.0"
-      }
-    },
-    "doctrine": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
-      "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
-      "dev": true,
-      "requires": {
-        "esutils": "^2.0.2"
-      }
-    },
-    "es-abstract": {
-      "version": "1.21.2",
-      "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.2.tgz",
-      "integrity": "sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg==",
-      "dev": true,
-      "requires": {
-        "array-buffer-byte-length": "^1.0.0",
-        "available-typed-arrays": "^1.0.5",
-        "call-bind": "^1.0.2",
-        "es-set-tostringtag": "^2.0.1",
-        "es-to-primitive": "^1.2.1",
-        "function.prototype.name": "^1.1.5",
-        "get-intrinsic": "^1.2.0",
-        "get-symbol-description": "^1.0.0",
-        "globalthis": "^1.0.3",
-        "gopd": "^1.0.1",
-        "has": "^1.0.3",
-        "has-property-descriptors": "^1.0.0",
-        "has-proto": "^1.0.1",
-        "has-symbols": "^1.0.3",
-        "internal-slot": "^1.0.5",
-        "is-array-buffer": "^3.0.2",
-        "is-callable": "^1.2.7",
-        "is-negative-zero": "^2.0.2",
-        "is-regex": "^1.1.4",
-        "is-shared-array-buffer": "^1.0.2",
-        "is-string": "^1.0.7",
-        "is-typed-array": "^1.1.10",
-        "is-weakref": "^1.0.2",
-        "object-inspect": "^1.12.3",
-        "object-keys": "^1.1.1",
-        "object.assign": "^4.1.4",
-        "regexp.prototype.flags": "^1.4.3",
-        "safe-regex-test": "^1.0.0",
-        "string.prototype.trim": "^1.2.7",
-        "string.prototype.trimend": "^1.0.6",
-        "string.prototype.trimstart": "^1.0.6",
-        "typed-array-length": "^1.0.4",
-        "unbox-primitive": "^1.0.2",
-        "which-typed-array": "^1.1.9"
-      }
-    },
-    "es-set-tostringtag": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz",
-      "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==",
-      "dev": true,
-      "requires": {
-        "get-intrinsic": "^1.1.3",
-        "has": "^1.0.3",
-        "has-tostringtag": "^1.0.0"
-      }
-    },
-    "es-shim-unscopables": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz",
-      "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==",
-      "dev": true,
-      "requires": {
-        "has": "^1.0.3"
+        "csstype": "^3.0.2"
       }
     },
-    "es-to-primitive": {
-      "version": "1.2.1",
-      "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz",
-      "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==",
+    "@types/react-dom": {
+      "version": "17.0.19",
+      "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.19.tgz",
+      "integrity": "sha512-PiYG40pnQRdPHnlf7tZnp0aQ6q9tspYr72vD61saO6zFCybLfMqwUCN0va1/P+86DXn18ZWeW30Bk7xlC5eEAQ==",
       "dev": true,
       "requires": {
-        "is-callable": "^1.1.4",
-        "is-date-object": "^1.0.1",
-        "is-symbol": "^1.0.2"
+        "@types/react": "^17"
       }
     },
+    "@types/scheduler": {
+      "version": "0.16.3",
+      "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz",
+      "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==",
+      "dev": true
+    },
+    "csstype": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz",
+      "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==",
+      "dev": true
+    },
     "esbuild": {
       "version": "0.15.18",
       "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.18.tgz",
@@ -4063,168 +996,6 @@
       "dev": true,
       "optional": true
     },
-    "escape-string-regexp": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
-      "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
-      "dev": true
-    },
-    "eslint": {
-      "version": "8.38.0",
-      "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.38.0.tgz",
-      "integrity": "sha512-pIdsD2jwlUGf/U38Jv97t8lq6HpaU/G9NKbYmpWpZGw3LdTNhZLbJePqxOXGB5+JEKfOPU/XLxYxFh03nr1KTg==",
-      "dev": true,
-      "requires": {
-        "@eslint-community/eslint-utils": "^4.2.0",
-        "@eslint-community/regexpp": "^4.4.0",
-        "@eslint/eslintrc": "^2.0.2",
-        "@eslint/js": "8.38.0",
-        "@humanwhocodes/config-array": "^0.11.8",
-        "@humanwhocodes/module-importer": "^1.0.1",
-        "@nodelib/fs.walk": "^1.2.8",
-        "ajv": "^6.10.0",
-        "chalk": "^4.0.0",
-        "cross-spawn": "^7.0.2",
-        "debug": "^4.3.2",
-        "doctrine": "^3.0.0",
-        "escape-string-regexp": "^4.0.0",
-        "eslint-scope": "^7.1.1",
-        "eslint-visitor-keys": "^3.4.0",
-        "espree": "^9.5.1",
-        "esquery": "^1.4.2",
-        "esutils": "^2.0.2",
-        "fast-deep-equal": "^3.1.3",
-        "file-entry-cache": "^6.0.1",
-        "find-up": "^5.0.0",
-        "glob-parent": "^6.0.2",
-        "globals": "^13.19.0",
-        "grapheme-splitter": "^1.0.4",
-        "ignore": "^5.2.0",
-        "import-fresh": "^3.0.0",
-        "imurmurhash": "^0.1.4",
-        "is-glob": "^4.0.0",
-        "is-path-inside": "^3.0.3",
-        "js-sdsl": "^4.1.4",
-        "js-yaml": "^4.1.0",
-        "json-stable-stringify-without-jsonify": "^1.0.1",
-        "levn": "^0.4.1",
-        "lodash.merge": "^4.6.2",
-        "minimatch": "^3.1.2",
-        "natural-compare": "^1.4.0",
-        "optionator": "^0.9.1",
-        "strip-ansi": "^6.0.1",
-        "strip-json-comments": "^3.1.0",
-        "text-table": "^0.2.0"
-      }
-    },
-    "eslint-plugin-react": {
-      "version": "7.32.2",
-      "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.32.2.tgz",
-      "integrity": "sha512-t2fBMa+XzonrrNkyVirzKlvn5RXzzPwRHtMvLAtVZrt8oxgnTQaYbU6SXTOO1mwQgp1y5+toMSKInnzGr0Knqg==",
-      "dev": true,
-      "requires": {
-        "array-includes": "^3.1.6",
-        "array.prototype.flatmap": "^1.3.1",
-        "array.prototype.tosorted": "^1.1.1",
-        "doctrine": "^2.1.0",
-        "estraverse": "^5.3.0",
-        "jsx-ast-utils": "^2.4.1 || ^3.0.0",
-        "minimatch": "^3.1.2",
-        "object.entries": "^1.1.6",
-        "object.fromentries": "^2.0.6",
-        "object.hasown": "^1.1.2",
-        "object.values": "^1.1.6",
-        "prop-types": "^15.8.1",
-        "resolve": "^2.0.0-next.4",
-        "semver": "^6.3.0",
-        "string.prototype.matchall": "^4.0.8"
-      },
-      "dependencies": {
-        "doctrine": {
-          "version": "2.1.0",
-          "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
-          "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==",
-          "dev": true,
-          "requires": {
-            "esutils": "^2.0.2"
-          }
-        },
-        "resolve": {
-          "version": "2.0.0-next.4",
-          "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.4.tgz",
-          "integrity": "sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==",
-          "dev": true,
-          "requires": {
-            "is-core-module": "^2.9.0",
-            "path-parse": "^1.0.7",
-            "supports-preserve-symlinks-flag": "^1.0.0"
-          }
-        },
-        "semver": {
-          "version": "6.3.0",
-          "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
-          "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
-          "dev": true
-        }
-      }
-    },
-    "eslint-scope": {
-      "version": "7.1.1",
-      "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz",
-      "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==",
-      "dev": true,
-      "requires": {
-        "esrecurse": "^4.3.0",
-        "estraverse": "^5.2.0"
-      }
-    },
-    "eslint-visitor-keys": {
-      "version": "3.4.0",
-      "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz",
-      "integrity": "sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ==",
-      "dev": true
-    },
-    "espree": {
-      "version": "9.5.1",
-      "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.1.tgz",
-      "integrity": "sha512-5yxtHSZXRSW5pvv3hAlXM5+/Oswi1AUFqBmbibKb5s6bp3rGIDkyXU6xCoyuuLhijr4SFwPrXRoZjz0AZDN9tg==",
-      "dev": true,
-      "requires": {
-        "acorn": "^8.8.0",
-        "acorn-jsx": "^5.3.2",
-        "eslint-visitor-keys": "^3.4.0"
-      }
-    },
-    "esquery": {
-      "version": "1.5.0",
-      "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz",
-      "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==",
-      "dev": true,
-      "requires": {
-        "estraverse": "^5.1.0"
-      }
-    },
-    "esrecurse": {
-      "version": "4.3.0",
-      "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
-      "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
-      "dev": true,
-      "requires": {
-        "estraverse": "^5.2.0"
-      }
-    },
-    "estraverse": {
-      "version": "5.3.0",
-      "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
-      "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
-      "dev": true
-    },
-    "esutils": {
-      "version": "2.0.3",
-      "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
-      "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
-      "dev": true
-    },
     "event-to-object": {
       "version": "0.1.2",
       "resolved": "https://registry.npmjs.org/event-to-object/-/event-to-object-0.1.2.tgz",
@@ -4233,121 +1004,11 @@
         "json-pointer": "^0.6.2"
       }
     },
-    "fast-deep-equal": {
-      "version": "3.1.3",
-      "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
-      "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
-      "dev": true
-    },
-    "fast-glob": {
-      "version": "3.2.12",
-      "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz",
-      "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==",
-      "dev": true,
-      "requires": {
-        "@nodelib/fs.stat": "^2.0.2",
-        "@nodelib/fs.walk": "^1.2.3",
-        "glob-parent": "^5.1.2",
-        "merge2": "^1.3.0",
-        "micromatch": "^4.0.4"
-      },
-      "dependencies": {
-        "glob-parent": {
-          "version": "5.1.2",
-          "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
-          "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
-          "dev": true,
-          "requires": {
-            "is-glob": "^4.0.1"
-          }
-        }
-      }
-    },
-    "fast-json-stable-stringify": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
-      "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
-      "dev": true
-    },
-    "fast-levenshtein": {
-      "version": "2.0.6",
-      "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
-      "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
-      "dev": true
-    },
-    "fastq": {
-      "version": "1.15.0",
-      "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz",
-      "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==",
-      "dev": true,
-      "requires": {
-        "reusify": "^1.0.4"
-      }
-    },
-    "file-entry-cache": {
-      "version": "6.0.1",
-      "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
-      "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
-      "dev": true,
-      "requires": {
-        "flat-cache": "^3.0.4"
-      }
-    },
-    "fill-range": {
-      "version": "7.0.1",
-      "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
-      "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
-      "dev": true,
-      "requires": {
-        "to-regex-range": "^5.0.1"
-      }
-    },
-    "find-up": {
-      "version": "5.0.0",
-      "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
-      "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
-      "dev": true,
-      "requires": {
-        "locate-path": "^6.0.0",
-        "path-exists": "^4.0.0"
-      }
-    },
-    "flat-cache": {
-      "version": "3.0.4",
-      "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz",
-      "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==",
-      "dev": true,
-      "requires": {
-        "flatted": "^3.1.0",
-        "rimraf": "^3.0.2"
-      }
-    },
-    "flatted": {
-      "version": "3.2.7",
-      "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz",
-      "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==",
-      "dev": true
-    },
-    "for-each": {
-      "version": "0.3.3",
-      "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz",
-      "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==",
-      "dev": true,
-      "requires": {
-        "is-callable": "^1.1.3"
-      }
-    },
     "foreach": {
       "version": "2.0.6",
       "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.6.tgz",
       "integrity": "sha512-k6GAGDyqLe9JaebCsFCoudPPWfihKu8pylYXRlqP1J7ms39iPoTtk2fviNglIeQEwdh0bQeKJ01ZPyuyQvKzwg=="
     },
-    "fs.realpath": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
-      "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
-      "dev": true
-    },
     "fsevents": {
       "version": "2.3.2",
       "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
@@ -4356,400 +1017,34 @@
       "optional": true
     },
     "function-bind": {
-      "version": "1.1.1",
-      "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
-      "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
-      "dev": true
-    },
-    "function.prototype.name": {
-      "version": "1.1.5",
-      "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz",
-      "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==",
-      "dev": true,
-      "requires": {
-        "call-bind": "^1.0.2",
-        "define-properties": "^1.1.3",
-        "es-abstract": "^1.19.0",
-        "functions-have-names": "^1.2.2"
-      }
-    },
-    "functions-have-names": {
-      "version": "1.2.3",
-      "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz",
-      "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==",
-      "dev": true
-    },
-    "get-intrinsic": {
-      "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz",
-      "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==",
-      "dev": true,
-      "requires": {
-        "function-bind": "^1.1.1",
-        "has": "^1.0.3",
-        "has-symbols": "^1.0.3"
-      }
-    },
-    "get-symbol-description": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz",
-      "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==",
-      "dev": true,
-      "requires": {
-        "call-bind": "^1.0.2",
-        "get-intrinsic": "^1.1.1"
-      }
-    },
-    "glob": {
-      "version": "7.2.3",
-      "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
-      "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
-      "dev": true,
-      "requires": {
-        "fs.realpath": "^1.0.0",
-        "inflight": "^1.0.4",
-        "inherits": "2",
-        "minimatch": "^3.1.1",
-        "once": "^1.3.0",
-        "path-is-absolute": "^1.0.0"
-      }
-    },
-    "glob-parent": {
-      "version": "6.0.2",
-      "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
-      "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
-      "dev": true,
-      "requires": {
-        "is-glob": "^4.0.3"
-      }
-    },
-    "globals": {
-      "version": "13.20.0",
-      "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz",
-      "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==",
-      "dev": true,
-      "requires": {
-        "type-fest": "^0.20.2"
-      }
-    },
-    "globalthis": {
-      "version": "1.0.3",
-      "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz",
-      "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==",
-      "dev": true,
-      "requires": {
-        "define-properties": "^1.1.3"
-      }
-    },
-    "globby": {
-      "version": "11.1.0",
-      "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz",
-      "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==",
-      "dev": true,
-      "requires": {
-        "array-union": "^2.1.0",
-        "dir-glob": "^3.0.1",
-        "fast-glob": "^3.2.9",
-        "ignore": "^5.2.0",
-        "merge2": "^1.4.1",
-        "slash": "^3.0.0"
-      }
-    },
-    "gopd": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
-      "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
-      "dev": true,
-      "requires": {
-        "get-intrinsic": "^1.1.3"
-      }
-    },
-    "grapheme-splitter": {
-      "version": "1.0.4",
-      "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz",
-      "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==",
-      "dev": true
-    },
-    "has": {
-      "version": "1.0.3",
-      "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
-      "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
-      "dev": true,
-      "requires": {
-        "function-bind": "^1.1.1"
-      }
-    },
-    "has-bigints": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz",
-      "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==",
-      "dev": true
-    },
-    "has-flag": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
-      "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
-      "dev": true
-    },
-    "has-property-descriptors": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz",
-      "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==",
-      "dev": true,
-      "requires": {
-        "get-intrinsic": "^1.1.1"
-      }
-    },
-    "has-proto": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz",
-      "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==",
-      "dev": true
-    },
-    "has-symbols": {
-      "version": "1.0.3",
-      "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
-      "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
-      "dev": true
-    },
-    "has-tostringtag": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz",
-      "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==",
-      "dev": true,
-      "requires": {
-        "has-symbols": "^1.0.2"
-      }
-    },
-    "ignore": {
-      "version": "5.2.4",
-      "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",
-      "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==",
-      "dev": true
-    },
-    "import-fresh": {
-      "version": "3.3.0",
-      "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
-      "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
-      "dev": true,
-      "requires": {
-        "parent-module": "^1.0.0",
-        "resolve-from": "^4.0.0"
-      }
-    },
-    "imurmurhash": {
-      "version": "0.1.4",
-      "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
-      "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
-      "dev": true
-    },
-    "inflight": {
-      "version": "1.0.6",
-      "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
-      "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
-      "dev": true,
-      "requires": {
-        "once": "^1.3.0",
-        "wrappy": "1"
-      }
-    },
-    "inherits": {
-      "version": "2.0.4",
-      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
-      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
-      "dev": true
-    },
-    "internal-slot": {
-      "version": "1.0.5",
-      "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz",
-      "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==",
-      "dev": true,
-      "requires": {
-        "get-intrinsic": "^1.2.0",
-        "has": "^1.0.3",
-        "side-channel": "^1.0.4"
-      }
-    },
-    "is-array-buffer": {
-      "version": "3.0.2",
-      "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz",
-      "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==",
-      "dev": true,
-      "requires": {
-        "call-bind": "^1.0.2",
-        "get-intrinsic": "^1.2.0",
-        "is-typed-array": "^1.1.10"
-      }
-    },
-    "is-bigint": {
-      "version": "1.0.4",
-      "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz",
-      "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==",
-      "dev": true,
-      "requires": {
-        "has-bigints": "^1.0.1"
-      }
-    },
-    "is-boolean-object": {
-      "version": "1.1.2",
-      "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz",
-      "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==",
-      "dev": true,
-      "requires": {
-        "call-bind": "^1.0.2",
-        "has-tostringtag": "^1.0.0"
-      }
-    },
-    "is-callable": {
-      "version": "1.2.7",
-      "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
-      "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==",
-      "dev": true
-    },
-    "is-core-module": {
-      "version": "2.12.0",
-      "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.0.tgz",
-      "integrity": "sha512-RECHCBCd/viahWmwj6enj19sKbHfJrddi/6cBDsNTKbNq0f7VeaUkBo60BqzvPqo/W54ChS62Z5qyun7cfOMqQ==",
-      "dev": true,
-      "requires": {
-        "has": "^1.0.3"
-      }
-    },
-    "is-date-object": {
-      "version": "1.0.5",
-      "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz",
-      "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==",
-      "dev": true,
-      "requires": {
-        "has-tostringtag": "^1.0.0"
-      }
-    },
-    "is-extglob": {
-      "version": "2.1.1",
-      "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
-      "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
-      "dev": true
-    },
-    "is-glob": {
-      "version": "4.0.3",
-      "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
-      "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
-      "dev": true,
-      "requires": {
-        "is-extglob": "^2.1.1"
-      }
-    },
-    "is-negative-zero": {
-      "version": "2.0.2",
-      "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz",
-      "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==",
-      "dev": true
-    },
-    "is-number": {
-      "version": "7.0.0",
-      "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
-      "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
-      "dev": true
-    },
-    "is-number-object": {
-      "version": "1.0.7",
-      "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz",
-      "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==",
-      "dev": true,
-      "requires": {
-        "has-tostringtag": "^1.0.0"
-      }
-    },
-    "is-path-inside": {
-      "version": "3.0.3",
-      "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
-      "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
-      "dev": true
-    },
-    "is-regex": {
-      "version": "1.1.4",
-      "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz",
-      "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==",
-      "dev": true,
-      "requires": {
-        "call-bind": "^1.0.2",
-        "has-tostringtag": "^1.0.0"
-      }
-    },
-    "is-shared-array-buffer": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz",
-      "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==",
-      "dev": true,
-      "requires": {
-        "call-bind": "^1.0.2"
-      }
-    },
-    "is-string": {
-      "version": "1.0.7",
-      "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz",
-      "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==",
-      "dev": true,
-      "requires": {
-        "has-tostringtag": "^1.0.0"
-      }
-    },
-    "is-symbol": {
-      "version": "1.0.4",
-      "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz",
-      "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==",
-      "dev": true,
-      "requires": {
-        "has-symbols": "^1.0.2"
-      }
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+      "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
+      "dev": true
     },
-    "is-typed-array": {
-      "version": "1.1.10",
-      "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz",
-      "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==",
+    "has": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+      "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
       "dev": true,
       "requires": {
-        "available-typed-arrays": "^1.0.5",
-        "call-bind": "^1.0.2",
-        "for-each": "^0.3.3",
-        "gopd": "^1.0.1",
-        "has-tostringtag": "^1.0.0"
+        "function-bind": "^1.1.1"
       }
     },
-    "is-weakref": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz",
-      "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==",
+    "is-core-module": {
+      "version": "2.12.0",
+      "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.0.tgz",
+      "integrity": "sha512-RECHCBCd/viahWmwj6enj19sKbHfJrddi/6cBDsNTKbNq0f7VeaUkBo60BqzvPqo/W54ChS62Z5qyun7cfOMqQ==",
       "dev": true,
       "requires": {
-        "call-bind": "^1.0.2"
+        "has": "^1.0.3"
       }
     },
-    "isexe": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
-      "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
-      "dev": true
-    },
-    "js-sdsl": {
-      "version": "4.4.0",
-      "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.0.tgz",
-      "integrity": "sha512-FfVSdx6pJ41Oa+CF7RDaFmTnCaFhua+SNYQX74riGOpl96x+2jQCqEfQ2bnXu/5DPCqlRuiqyvTJM0Qjz26IVg==",
-      "dev": true
-    },
     "js-tokens": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
-      "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
-    },
-    "js-yaml": {
-      "version": "4.1.0",
-      "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
-      "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
-      "dev": true,
-      "requires": {
-        "argparse": "^2.0.1"
-      }
+      "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+      "peer": true
     },
     "json-pointer": {
       "version": "0.6.2",
@@ -4759,258 +1054,26 @@
         "foreach": "^2.0.4"
       }
     },
-    "json-schema-traverse": {
-      "version": "0.4.1",
-      "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
-      "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
-      "dev": true
-    },
-    "json-stable-stringify-without-jsonify": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
-      "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
-      "dev": true
-    },
-    "jsx-ast-utils": {
-      "version": "3.3.3",
-      "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.3.tgz",
-      "integrity": "sha512-fYQHZTZ8jSfmWZ0iyzfwiU4WDX4HpHbMCZ3gPlWYiCl3BoeOTsqKBqnTVfH2rYT7eP5c3sVbeSPHnnJOaTrWiw==",
-      "dev": true,
-      "requires": {
-        "array-includes": "^3.1.5",
-        "object.assign": "^4.1.3"
-      }
-    },
-    "levn": {
-      "version": "0.4.1",
-      "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
-      "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
-      "dev": true,
-      "requires": {
-        "prelude-ls": "^1.2.1",
-        "type-check": "~0.4.0"
-      }
-    },
-    "locate-path": {
-      "version": "6.0.0",
-      "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
-      "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
-      "dev": true,
-      "requires": {
-        "p-locate": "^5.0.0"
-      }
-    },
-    "lodash.merge": {
-      "version": "4.6.2",
-      "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
-      "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
-      "dev": true
-    },
     "loose-envify": {
       "version": "1.4.0",
       "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
       "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+      "peer": true,
       "requires": {
         "js-tokens": "^3.0.0 || ^4.0.0"
       }
     },
-    "lru-cache": {
-      "version": "6.0.0",
-      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
-      "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
-      "dev": true,
-      "requires": {
-        "yallist": "^4.0.0"
-      }
-    },
-    "merge2": {
-      "version": "1.4.1",
-      "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
-      "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
-      "dev": true
-    },
-    "micromatch": {
-      "version": "4.0.5",
-      "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
-      "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
-      "dev": true,
-      "requires": {
-        "braces": "^3.0.2",
-        "picomatch": "^2.3.1"
-      }
-    },
-    "minimatch": {
-      "version": "3.1.2",
-      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
-      "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
-      "dev": true,
-      "requires": {
-        "brace-expansion": "^1.1.7"
-      }
-    },
-    "ms": {
-      "version": "2.1.2",
-      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
-      "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
-      "dev": true
-    },
     "nanoid": {
       "version": "3.3.6",
       "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
       "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==",
       "dev": true
     },
-    "natural-compare": {
-      "version": "1.4.0",
-      "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
-      "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
-      "dev": true
-    },
-    "natural-compare-lite": {
-      "version": "1.4.0",
-      "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz",
-      "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==",
-      "dev": true
-    },
     "object-assign": {
       "version": "4.1.1",
       "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
-      "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="
-    },
-    "object-inspect": {
-      "version": "1.12.3",
-      "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz",
-      "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==",
-      "dev": true
-    },
-    "object-keys": {
-      "version": "1.1.1",
-      "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
-      "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
-      "dev": true
-    },
-    "object.assign": {
-      "version": "4.1.4",
-      "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz",
-      "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==",
-      "dev": true,
-      "requires": {
-        "call-bind": "^1.0.2",
-        "define-properties": "^1.1.4",
-        "has-symbols": "^1.0.3",
-        "object-keys": "^1.1.1"
-      }
-    },
-    "object.entries": {
-      "version": "1.1.6",
-      "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.6.tgz",
-      "integrity": "sha512-leTPzo4Zvg3pmbQ3rDK69Rl8GQvIqMWubrkxONG9/ojtFE2rD9fjMKfSI5BxW3osRH1m6VdzmqK8oAY9aT4x5w==",
-      "dev": true,
-      "requires": {
-        "call-bind": "^1.0.2",
-        "define-properties": "^1.1.4",
-        "es-abstract": "^1.20.4"
-      }
-    },
-    "object.fromentries": {
-      "version": "2.0.6",
-      "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.6.tgz",
-      "integrity": "sha512-VciD13dswC4j1Xt5394WR4MzmAQmlgN72phd/riNp9vtD7tp4QQWJ0R4wvclXcafgcYK8veHRed2W6XeGBvcfg==",
-      "dev": true,
-      "requires": {
-        "call-bind": "^1.0.2",
-        "define-properties": "^1.1.4",
-        "es-abstract": "^1.20.4"
-      }
-    },
-    "object.hasown": {
-      "version": "1.1.2",
-      "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.2.tgz",
-      "integrity": "sha512-B5UIT3J1W+WuWIU55h0mjlwaqxiE5vYENJXIXZ4VFe05pNYrkKuK0U/6aFcb0pKywYJh7IhfoqUfKVmrJJHZHw==",
-      "dev": true,
-      "requires": {
-        "define-properties": "^1.1.4",
-        "es-abstract": "^1.20.4"
-      }
-    },
-    "object.values": {
-      "version": "1.1.6",
-      "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz",
-      "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==",
-      "dev": true,
-      "requires": {
-        "call-bind": "^1.0.2",
-        "define-properties": "^1.1.4",
-        "es-abstract": "^1.20.4"
-      }
-    },
-    "once": {
-      "version": "1.4.0",
-      "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
-      "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
-      "dev": true,
-      "requires": {
-        "wrappy": "1"
-      }
-    },
-    "optionator": {
-      "version": "0.9.1",
-      "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",
-      "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==",
-      "dev": true,
-      "requires": {
-        "deep-is": "^0.1.3",
-        "fast-levenshtein": "^2.0.6",
-        "levn": "^0.4.1",
-        "prelude-ls": "^1.2.1",
-        "type-check": "^0.4.0",
-        "word-wrap": "^1.2.3"
-      }
-    },
-    "p-limit": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
-      "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
-      "dev": true,
-      "requires": {
-        "yocto-queue": "^0.1.0"
-      }
-    },
-    "p-locate": {
-      "version": "5.0.0",
-      "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
-      "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
-      "dev": true,
-      "requires": {
-        "p-limit": "^3.0.2"
-      }
-    },
-    "parent-module": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
-      "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
-      "dev": true,
-      "requires": {
-        "callsites": "^3.0.0"
-      }
-    },
-    "path-exists": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
-      "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
-      "dev": true
-    },
-    "path-is-absolute": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
-      "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
-      "dev": true
-    },
-    "path-key": {
-      "version": "3.1.1",
-      "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
-      "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
-      "dev": true
+      "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+      "peer": true
     },
     "path-parse": {
       "version": "1.0.7",
@@ -5018,24 +1081,12 @@
       "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
       "dev": true
     },
-    "path-type": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
-      "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
-      "dev": true
-    },
     "picocolors": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
       "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
       "dev": true
     },
-    "picomatch": {
-      "version": "2.3.1",
-      "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
-      "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
-      "dev": true
-    },
     "postcss": {
       "version": "8.4.21",
       "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz",
@@ -5052,41 +1103,6 @@
       "resolved": "https://registry.npmjs.org/preact/-/preact-10.13.2.tgz",
       "integrity": "sha512-q44QFLhOhty2Bd0Y46fnYW0gD/cbVM9dUVtNTDKPcdXSMA7jfY+Jpd6rk3GB0lcQss0z5s/6CmVP0Z/hV+g6pw=="
     },
-    "prelude-ls": {
-      "version": "1.2.1",
-      "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
-      "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
-      "dev": true
-    },
-    "prettier": {
-      "version": "3.0.0-alpha.6",
-      "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.0-alpha.6.tgz",
-      "integrity": "sha512-AdbQSZ6Oo+iy9Ekzmsgno05P1uX2vqPkjOMJqRfP8hTe+m6iDw4Nt7bPFpWZ/HYCU+3f0P5U0o2ghxQwwkLH7A==",
-      "dev": true
-    },
-    "prop-types": {
-      "version": "15.8.1",
-      "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
-      "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
-      "dev": true,
-      "requires": {
-        "loose-envify": "^1.4.0",
-        "object-assign": "^4.1.1",
-        "react-is": "^16.13.1"
-      }
-    },
-    "punycode": {
-      "version": "2.3.0",
-      "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz",
-      "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==",
-      "dev": true
-    },
-    "queue-microtask": {
-      "version": "1.2.3",
-      "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
-      "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
-      "dev": true
-    },
     "react": {
       "version": "17.0.2",
       "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz",
@@ -5108,23 +1124,6 @@
         "scheduler": "^0.20.2"
       }
     },
-    "react-is": {
-      "version": "16.13.1",
-      "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
-      "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
-      "dev": true
-    },
-    "regexp.prototype.flags": {
-      "version": "1.4.3",
-      "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz",
-      "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==",
-      "dev": true,
-      "requires": {
-        "call-bind": "^1.0.2",
-        "define-properties": "^1.1.3",
-        "functions-have-names": "^1.2.2"
-      }
-    },
     "resolve": {
       "version": "1.22.2",
       "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz",
@@ -5136,27 +1135,6 @@
         "supports-preserve-symlinks-flag": "^1.0.0"
       }
     },
-    "resolve-from": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
-      "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
-      "dev": true
-    },
-    "reusify": {
-      "version": "1.0.4",
-      "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
-      "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
-      "dev": true
-    },
-    "rimraf": {
-      "version": "3.0.2",
-      "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
-      "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
-      "dev": true,
-      "requires": {
-        "glob": "^7.1.3"
-      }
-    },
     "rollup": {
       "version": "2.79.1",
       "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz",
@@ -5166,26 +1144,6 @@
         "fsevents": "~2.3.2"
       }
     },
-    "run-parallel": {
-      "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
-      "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
-      "dev": true,
-      "requires": {
-        "queue-microtask": "^1.2.2"
-      }
-    },
-    "safe-regex-test": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz",
-      "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==",
-      "dev": true,
-      "requires": {
-        "call-bind": "^1.0.2",
-        "get-intrinsic": "^1.1.3",
-        "is-regex": "^1.1.4"
-      }
-    },
     "scheduler": {
       "version": "0.20.2",
       "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz",
@@ -5196,210 +1154,28 @@
         "object-assign": "^4.1.1"
       }
     },
-    "semver": {
-      "version": "7.4.0",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-7.4.0.tgz",
-      "integrity": "sha512-RgOxM8Mw+7Zus0+zcLEUn8+JfoLpj/huFTItQy2hsM4khuC1HYRDp0cU482Ewn/Fcy6bCjufD8vAj7voC66KQw==",
-      "dev": true,
-      "requires": {
-        "lru-cache": "^6.0.0"
-      }
-    },
-    "shebang-command": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
-      "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
-      "dev": true,
-      "requires": {
-        "shebang-regex": "^3.0.0"
-      }
-    },
-    "shebang-regex": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
-      "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
-      "dev": true
-    },
-    "side-channel": {
-      "version": "1.0.4",
-      "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
-      "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
-      "dev": true,
-      "requires": {
-        "call-bind": "^1.0.0",
-        "get-intrinsic": "^1.0.2",
-        "object-inspect": "^1.9.0"
-      }
-    },
-    "slash": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
-      "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
-      "dev": true
-    },
     "source-map-js": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
       "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
       "dev": true
     },
-    "string.prototype.matchall": {
-      "version": "4.0.8",
-      "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.8.tgz",
-      "integrity": "sha512-6zOCOcJ+RJAQshcTvXPHoxoQGONa3e/Lqx90wUA+wEzX78sg5Bo+1tQo4N0pohS0erG9qtCqJDjNCQBjeWVxyg==",
-      "dev": true,
-      "requires": {
-        "call-bind": "^1.0.2",
-        "define-properties": "^1.1.4",
-        "es-abstract": "^1.20.4",
-        "get-intrinsic": "^1.1.3",
-        "has-symbols": "^1.0.3",
-        "internal-slot": "^1.0.3",
-        "regexp.prototype.flags": "^1.4.3",
-        "side-channel": "^1.0.4"
-      }
-    },
-    "string.prototype.trim": {
-      "version": "1.2.7",
-      "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz",
-      "integrity": "sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==",
-      "dev": true,
-      "requires": {
-        "call-bind": "^1.0.2",
-        "define-properties": "^1.1.4",
-        "es-abstract": "^1.20.4"
-      }
-    },
-    "string.prototype.trimend": {
-      "version": "1.0.6",
-      "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz",
-      "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==",
-      "dev": true,
-      "requires": {
-        "call-bind": "^1.0.2",
-        "define-properties": "^1.1.4",
-        "es-abstract": "^1.20.4"
-      }
-    },
-    "string.prototype.trimstart": {
-      "version": "1.0.6",
-      "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz",
-      "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==",
-      "dev": true,
-      "requires": {
-        "call-bind": "^1.0.2",
-        "define-properties": "^1.1.4",
-        "es-abstract": "^1.20.4"
-      }
-    },
-    "strip-ansi": {
-      "version": "6.0.1",
-      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
-      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
-      "dev": true,
-      "requires": {
-        "ansi-regex": "^5.0.1"
-      }
-    },
-    "strip-json-comments": {
-      "version": "3.1.1",
-      "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
-      "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
-      "dev": true
-    },
-    "supports-color": {
-      "version": "7.2.0",
-      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
-      "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
-      "dev": true,
-      "requires": {
-        "has-flag": "^4.0.0"
-      }
-    },
     "supports-preserve-symlinks-flag": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
       "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
       "dev": true
     },
-    "text-table": {
-      "version": "0.2.0",
-      "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
-      "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
-      "dev": true
-    },
-    "to-regex-range": {
-      "version": "5.0.1",
-      "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
-      "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
-      "dev": true,
-      "requires": {
-        "is-number": "^7.0.0"
-      }
-    },
-    "tslib": {
-      "version": "1.14.1",
-      "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
-      "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
-      "dev": true
-    },
-    "type-check": {
-      "version": "0.4.0",
-      "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
-      "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
-      "dev": true,
-      "requires": {
-        "prelude-ls": "^1.2.1"
-      }
-    },
-    "type-fest": {
-      "version": "0.20.2",
-      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
-      "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
-      "dev": true
-    },
-    "typed-array-length": {
-      "version": "1.0.4",
-      "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz",
-      "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==",
-      "dev": true,
-      "requires": {
-        "call-bind": "^1.0.2",
-        "for-each": "^0.3.3",
-        "is-typed-array": "^1.1.9"
-      }
-    },
     "typescript": {
       "version": "4.9.5",
       "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
       "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
       "dev": true
     },
-    "unbox-primitive": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz",
-      "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==",
-      "dev": true,
-      "requires": {
-        "call-bind": "^1.0.2",
-        "has-bigints": "^1.0.2",
-        "has-symbols": "^1.0.3",
-        "which-boxed-primitive": "^1.0.2"
-      }
-    },
-    "uri-js": {
-      "version": "4.4.1",
-      "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
-      "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
-      "dev": true,
-      "requires": {
-        "punycode": "^2.1.0"
-      }
-    },
     "vite": {
-      "version": "3.2.5",
-      "resolved": "https://registry.npmjs.org/vite/-/vite-3.2.5.tgz",
-      "integrity": "sha512-4mVEpXpSOgrssFZAOmGIr85wPHKvaDAcXqxVxVRZhljkJOMZi1ibLibzjLHzJvcok8BMguLc7g1W6W/GqZbLdQ==",
+      "version": "3.2.7",
+      "resolved": "https://registry.npmjs.org/vite/-/vite-3.2.7.tgz",
+      "integrity": "sha512-29pdXjk49xAP0QBr0xXqu2s5jiQIXNvE/xwd0vUizYT2Hzqe4BksNNoWllFVXJf4eLZ+UlVQmXfB4lWrc+t18g==",
       "dev": true,
       "requires": {
         "esbuild": "^0.15.9",
@@ -5408,66 +1184,6 @@
         "resolve": "^1.22.1",
         "rollup": "^2.79.1"
       }
-    },
-    "which": {
-      "version": "2.0.2",
-      "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
-      "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
-      "dev": true,
-      "requires": {
-        "isexe": "^2.0.0"
-      }
-    },
-    "which-boxed-primitive": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz",
-      "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==",
-      "dev": true,
-      "requires": {
-        "is-bigint": "^1.0.1",
-        "is-boolean-object": "^1.1.0",
-        "is-number-object": "^1.0.4",
-        "is-string": "^1.0.5",
-        "is-symbol": "^1.0.3"
-      }
-    },
-    "which-typed-array": {
-      "version": "1.1.9",
-      "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz",
-      "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==",
-      "dev": true,
-      "requires": {
-        "available-typed-arrays": "^1.0.5",
-        "call-bind": "^1.0.2",
-        "for-each": "^0.3.3",
-        "gopd": "^1.0.1",
-        "has-tostringtag": "^1.0.0",
-        "is-typed-array": "^1.1.10"
-      }
-    },
-    "word-wrap": {
-      "version": "1.2.3",
-      "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
-      "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
-      "dev": true
-    },
-    "wrappy": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
-      "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
-      "dev": true
-    },
-    "yallist": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
-      "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
-      "dev": true
-    },
-    "yocto-queue": {
-      "version": "0.1.0",
-      "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
-      "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
-      "dev": true
     }
   }
 }
diff --git a/src/js/app/package.json b/src/js/app/package.json
index b9371dba3..40ce94739 100644
--- a/src/js/app/package.json
+++ b/src/js/app/package.json
@@ -12,7 +12,7 @@
     "@types/react": "^17.0",
     "@types/react-dom": "^17.0",
     "typescript": "^4.9.5",
-    "vite": "^3.1.8"
+    "vite": "^3.2.7"
   },
   "repository": {
     "type": "git",

From 1dcdc9c30ee3451d0889bc90536ed00dcd883a47 Mon Sep 17 00:00:00 2001
From: Ryan Morshead <ryan.morshead@gmail.com>
Date: Wed, 14 Jun 2023 13:08:24 -0600
Subject: [PATCH 31/37] Update LICENSE date

---
 LICENSE | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/LICENSE b/LICENSE
index 5caf76c93..060079c01 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
 The MIT License (MIT)
 
-Copyright (c) 2019-2022 Ryan S. Morshead
+Copyright (c) 2019 Ryan S. Morshead
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal

From 678afe01f76dd7c5a7fc8f9f5f08b1ed8f6a83be Mon Sep 17 00:00:00 2001
From: Ryan Morshead <ryan.morshead@gmail.com>
Date: Wed, 14 Jun 2023 19:52:28 -0700
Subject: [PATCH 32/37] Add contributor license agreement

---
 .github/pull_request_template.md | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index a423818ca..d889c598a 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -1,3 +1,5 @@
+*By submitting this pull request you agree that all contributions to this project are made under the MIT license.*
+
 ## Issues
 
 <!-- Link the issues this change resolves, or describe them -->

From a3d79aa8f5743ddbe4b0c229832bcf48d86d19e1 Mon Sep 17 00:00:00 2001
From: Ryan Morshead <ryan.morshead@gmail.com>
Date: Wed, 14 Jun 2023 22:18:43 -0600
Subject: [PATCH 33/37] report better error for attrs that cannot be serialized
 (#1008)

* report better error for attrs that cannot be serialized

also misc changes to config options to make this eaiser
in particular ability to specify options as parents

* add more tests

* no need to be clever - just inherit

* fix tests

* ignore missing cov

* add test for non-json serializable attrs

* add changelog entry

* fix tests

* try node 16?

https://github.com/nodesource/distributions#using-ubuntu-3

* install npm explicitey

* try install in different order

* update first

* explicitely install npm i guess...
---
 docs/Dockerfile                             |  4 +-
 docs/source/about/changelog.rst             |  5 +-
 src/py/reactpy/reactpy/_option.py           | 51 +++++++++++-----
 src/py/reactpy/reactpy/backend/_common.py   |  2 +-
 src/py/reactpy/reactpy/config.py            | 67 ++++++++++++++-------
 src/py/reactpy/reactpy/core/serve.py        | 14 ++++-
 src/py/reactpy/reactpy/core/vdom.py         | 15 +++--
 src/py/reactpy/tests/test__option.py        | 30 ++++++++-
 src/py/reactpy/tests/test_config.py         | 24 ++++++++
 src/py/reactpy/tests/test_core/test_vdom.py | 44 ++++++++------
 src/py/reactpy/tests/test_utils.py          |  4 --
 11 files changed, 185 insertions(+), 75 deletions(-)

diff --git a/docs/Dockerfile b/docs/Dockerfile
index 76a8ad7ee..39b9c51be 100644
--- a/docs/Dockerfile
+++ b/docs/Dockerfile
@@ -4,8 +4,8 @@ WORKDIR /app/
 
 # Install NodeJS
 # --------------
-RUN curl -sL https://deb.nodesource.com/setup_14.x  | bash -
-RUN apt-get install -yq nodejs build-essential
+RUN curl -fsSL https://deb.nodesource.com/setup_16.x | bash -
+RUN apt-get install -y build-essential nodejs npm
 RUN npm install -g npm@8.5.0
 
 # Install Poetry
diff --git a/docs/source/about/changelog.rst b/docs/source/about/changelog.rst
index f739ce980..62e7d47eb 100644
--- a/docs/source/about/changelog.rst
+++ b/docs/source/about/changelog.rst
@@ -23,7 +23,10 @@ more info, see the :ref:`Contributor Guide <Creating a Changelog Entry>`.
 Unreleased
 ----------
 
-No changes.
+**Fixed**
+
+- :issue:`930` - better traceback for JSON serialization errors (via :pull:`1008`)
+- :issue:`437` - explain that JS component attributes must be JSON (via :pull:`1008`)
 
 
 v1.0.0
diff --git a/src/py/reactpy/reactpy/_option.py b/src/py/reactpy/reactpy/_option.py
index 1421f33a3..09d0304a9 100644
--- a/src/py/reactpy/reactpy/_option.py
+++ b/src/py/reactpy/reactpy/_option.py
@@ -8,6 +8,7 @@
 
 _O = TypeVar("_O")
 logger = getLogger(__name__)
+UNDEFINED = cast(Any, object())
 
 
 class Option(Generic[_O]):
@@ -16,8 +17,9 @@ class Option(Generic[_O]):
     def __init__(
         self,
         name: str,
-        default: _O | Option[_O],
+        default: _O = UNDEFINED,
         mutable: bool = True,
+        parent: Option[_O] | None = None,
         validator: Callable[[Any], _O] = lambda x: cast(_O, x),
     ) -> None:
         self._name = name
@@ -28,12 +30,15 @@ def __init__(
         if name in os.environ:
             self._current = validator(os.environ[name])
 
-        self._default: _O
-        if isinstance(default, Option):
-            self._default = default.default
-            default.subscribe(lambda value: setattr(self, "_default", value))
-        else:
+        if parent is not None:
+            if not (parent.mutable and self.mutable):
+                raise TypeError("Parent and child options must be mutable")
+            self._default = parent.default
+            parent.subscribe(self.set_current)
+        elif default is not UNDEFINED:
             self._default = default
+        else:
+            raise TypeError("Must specify either a default or a parent option")
 
         logger.debug(f"{self._name}={self.current}")
 
@@ -81,11 +86,19 @@ def set_current(self, new: Any) -> None:
 
         Raises a ``TypeError`` if this option is not :attr:`Option.mutable`.
         """
+        old = self.current
+        if new is old:
+            return None
+
         if not self._mutable:
             msg = f"{self} cannot be modified after initial load"
             raise TypeError(msg)
-        old = self.current
-        new = self._current = self._validator(new)
+
+        try:
+            new = self._current = self._validator(new)
+        except ValueError as error:
+            raise ValueError(f"Invalid value for {self._name}: {new!r}") from error
+
         logger.debug(f"{self._name}={self._current}")
         if new != old:
             for sub_func in self._subscribers:
@@ -119,15 +132,23 @@ def __repr__(self) -> str:
         return f"Option({self._name}={self.current!r})"
 
 
-class DeprecatedOption(Option[_O]):  # nocov
-    def __init__(self, message: str, *args: Any, **kwargs: Any) -> None:
-        self._deprecation_message = message
+class DeprecatedOption(Option[_O]):
+    """An option that will warn when it is accessed"""
+
+    def __init__(self, *args: Any, message: str, **kwargs: Any) -> None:
         super().__init__(*args, **kwargs)
+        self._deprecation_message = message
 
     @Option.current.getter  # type: ignore
     def current(self) -> _O:
-        warn(
-            self._deprecation_message,
-            DeprecationWarning,
-        )
+        try:
+            # we access the current value during init to debug log it
+            # no need to warn unless it's actually used. since this attr
+            # is only set after super().__init__ is called, we can check
+            # for it to determine if it's being accessed by a user.
+            msg = self._deprecation_message
+        except AttributeError:
+            pass
+        else:
+            warn(msg, DeprecationWarning)
         return super().current
diff --git a/src/py/reactpy/reactpy/backend/_common.py b/src/py/reactpy/reactpy/backend/_common.py
index 80b4eeee1..17983a033 100644
--- a/src/py/reactpy/reactpy/backend/_common.py
+++ b/src/py/reactpy/reactpy/backend/_common.py
@@ -114,7 +114,7 @@ def vdom_head_elements_to_html(head: Sequence[VdomDict] | VdomDict | str) -> str
             head = cast(VdomDict, {**head, "tagName": ""})
         return vdom_to_html(head)
     else:
-        return vdom_to_html(html._(head))
+        return vdom_to_html(html._(*head))
 
 
 @dataclass
diff --git a/src/py/reactpy/reactpy/config.py b/src/py/reactpy/reactpy/config.py
index 6dc29096c..8371e6d08 100644
--- a/src/py/reactpy/reactpy/config.py
+++ b/src/py/reactpy/reactpy/config.py
@@ -3,44 +3,65 @@
 variables or, for those which allow it, a programmatic interface.
 """
 
+from __future__ import annotations
+
 from pathlib import Path
 from tempfile import TemporaryDirectory
 
-from reactpy._option import Option as _Option
+from reactpy._option import Option
 
-REACTPY_DEBUG_MODE = _Option(
-    "REACTPY_DEBUG_MODE",
-    default=False,
-    validator=lambda x: bool(int(x)),
-)
-"""This immutable option turns on/off debug mode
+TRUE_VALUES = {"true", "1"}
+FALSE_VALUES = {"false", "0"}
 
-The string values ``1`` and ``0`` are mapped to ``True`` and ``False`` respectively.
 
-When debug is on, extra validation measures are applied that negatively impact
-performance but can be used to catch bugs during development. Additionally, the default
-log level for ReactPy is set to ``DEBUG``.
-"""
+def boolean(value: str | bool | int) -> bool:
+    if isinstance(value, bool):
+        return value
+    elif isinstance(value, int):
+        return bool(value)
+    elif not isinstance(value, str):
+        raise TypeError(f"Expected str or bool, got {type(value).__name__}")
+
+    if value.lower() in TRUE_VALUES:
+        return True
+    elif value.lower() in FALSE_VALUES:
+        return False
+    else:
+        raise ValueError(
+            f"Invalid boolean value {value!r} - expected "
+            f"one of {list(TRUE_VALUES | FALSE_VALUES)}"
+        )
+
 
-REACTPY_CHECK_VDOM_SPEC = _Option(
-    "REACTPY_CHECK_VDOM_SPEC",
-    default=REACTPY_DEBUG_MODE,
-    validator=lambda x: bool(int(x)),
+REACTPY_DEBUG_MODE = Option(
+    "REACTPY_DEBUG_MODE", default=False, validator=boolean, mutable=True
 )
-"""This immutable option turns on/off checks which ensure VDOM is rendered to spec
+"""Get extra logs and validation checks at the cost of performance.
 
-The string values ``1`` and ``0`` are mapped to ``True`` and ``False`` respectively.
+This will enable the following:
 
-By default this check is off. When ``REACTPY_DEBUG_MODE=1`` this will be turned on but can
-be manually disablled by setting ``REACTPY_CHECK_VDOM_SPEC=0`` in addition.
+- :data:`REACTPY_CHECK_VDOM_SPEC`
+- :data:`REACTPY_CHECK_JSON_ATTRS`
+"""
+
+REACTPY_CHECK_VDOM_SPEC = Option("REACTPY_CHECK_VDOM_SPEC", parent=REACTPY_DEBUG_MODE)
+"""Checks which ensure VDOM is rendered to spec
 
 For more info on the VDOM spec, see here: :ref:`VDOM JSON Schema`
 """
 
-# Because these web modules will be linked dynamically at runtime this can be temporary
+REACTPY_CHECK_JSON_ATTRS = Option("REACTPY_CHECK_JSON_ATTRS", parent=REACTPY_DEBUG_MODE)
+"""Checks that all VDOM attributes are JSON serializable
+
+The VDOM spec is not able to enforce this on its own since attributes could anything.
+"""
+
+# Because these web modules will be linked dynamically at runtime this can be temporary.
+# Assigning to a variable here ensures that the directory is not deleted until the end
+# of the program.
 _DEFAULT_WEB_MODULES_DIR = TemporaryDirectory()
 
-REACTPY_WEB_MODULES_DIR = _Option(
+REACTPY_WEB_MODULES_DIR = Option(
     "REACTPY_WEB_MODULES_DIR",
     default=Path(_DEFAULT_WEB_MODULES_DIR.name),
     validator=Path,
@@ -52,7 +73,7 @@
 set of publicly available APIs for working with the client.
 """
 
-REACTPY_TESTING_DEFAULT_TIMEOUT = _Option(
+REACTPY_TESTING_DEFAULT_TIMEOUT = Option(
     "REACTPY_TESTING_DEFAULT_TIMEOUT",
     5.0,
     mutable=False,
diff --git a/src/py/reactpy/reactpy/core/serve.py b/src/py/reactpy/reactpy/core/serve.py
index 61a7e4ce6..3a530e854 100644
--- a/src/py/reactpy/reactpy/core/serve.py
+++ b/src/py/reactpy/reactpy/core/serve.py
@@ -7,6 +7,7 @@
 from anyio import create_task_group
 from anyio.abc import TaskGroup
 
+from reactpy.config import REACTPY_DEBUG_MODE
 from reactpy.core.types import LayoutEventMessage, LayoutType, LayoutUpdateMessage
 
 logger = getLogger(__name__)
@@ -49,7 +50,18 @@ async def _single_outgoing_loop(
     layout: LayoutType[LayoutUpdateMessage, LayoutEventMessage], send: SendCoroutine
 ) -> None:
     while True:
-        await send(await layout.render())
+        update = await layout.render()
+        try:
+            await send(update)
+        except Exception:  # nocov
+            if not REACTPY_DEBUG_MODE.current:
+                msg = (
+                    "Failed to send update. More info may be available "
+                    "if you enabling debug mode by setting "
+                    "`reactpy.config.REACTPY_DEBUG_MODE.current = True`."
+                )
+                logger.error(msg)
+            raise
 
 
 async def _single_incoming_loop(
diff --git a/src/py/reactpy/reactpy/core/vdom.py b/src/py/reactpy/reactpy/core/vdom.py
index 0548c6afc..840a09c7c 100644
--- a/src/py/reactpy/reactpy/core/vdom.py
+++ b/src/py/reactpy/reactpy/core/vdom.py
@@ -1,6 +1,6 @@
 from __future__ import annotations
 
-import logging
+import json
 from collections.abc import Mapping, Sequence
 from functools import wraps
 from typing import Any, Protocol, cast, overload
@@ -8,7 +8,7 @@
 from fastjsonschema import compile as compile_json_schema
 
 from reactpy._warnings import warn
-from reactpy.config import REACTPY_DEBUG_MODE
+from reactpy.config import REACTPY_CHECK_JSON_ATTRS, REACTPY_DEBUG_MODE
 from reactpy.core._f_back import f_module_name
 from reactpy.core.events import EventHandler, to_event_handler_function
 from reactpy.core.types import (
@@ -25,9 +25,6 @@
     VdomJson,
 )
 
-logger = logging.getLogger()
-
-
 VDOM_JSON_SCHEMA = {
     "$schema": "http://json-schema.org/draft-07/schema",
     "$ref": "#/definitions/element",
@@ -199,6 +196,8 @@ def vdom(
     attributes, event_handlers = separate_attributes_and_event_handlers(attributes)
 
     if attributes:
+        if REACTPY_CHECK_JSON_ATTRS.current:
+            json.dumps(attributes)
         model["attributes"] = attributes
 
     if children:
@@ -325,18 +324,18 @@ def _is_single_child(value: Any) -> bool:
 
 def _validate_child_key_integrity(value: Any) -> None:
     if hasattr(value, "__iter__") and not hasattr(value, "__len__"):
-        logger.error(
+        warn(
             f"Did not verify key-path integrity of children in generator {value} "
             "- pass a sequence (i.e. list of finite length) in order to verify"
         )
     else:
         for child in value:
             if isinstance(child, ComponentType) and child.key is None:
-                logger.error(f"Key not specified for child in list {child}")
+                warn(f"Key not specified for child in list {child}", UserWarning)
             elif isinstance(child, Mapping) and "key" not in child:
                 # remove 'children' to reduce log spam
                 child_copy = {**child, "children": _EllipsisRepr()}
-                logger.error(f"Key not specified for child in list {child_copy}")
+                warn(f"Key not specified for child in list {child_copy}", UserWarning)
 
 
 class _CustomVdomDictConstructor(Protocol):
diff --git a/src/py/reactpy/tests/test__option.py b/src/py/reactpy/tests/test__option.py
index 63f2fada8..929e17488 100644
--- a/src/py/reactpy/tests/test__option.py
+++ b/src/py/reactpy/tests/test__option.py
@@ -33,7 +33,7 @@ def test_option_validator():
     opt.current = "0"
     assert opt.current is False
 
-    with pytest.raises(ValueError, match="invalid literal for int"):
+    with pytest.raises(ValueError, match="Invalid value"):
         opt.current = "not-an-int"
 
 
@@ -102,10 +102,36 @@ def test_option_subscribe():
 
 
 def test_deprecated_option():
-    opt = DeprecatedOption("is deprecated!", "A_FAKE_OPTION", None)
+    opt = DeprecatedOption("A_FAKE_OPTION", None, message="is deprecated!")
 
     with pytest.warns(DeprecationWarning, match="is deprecated!"):
         assert opt.current is None
 
     with pytest.warns(DeprecationWarning, match="is deprecated!"):
         opt.current = "something"
+
+
+def test_option_parent():
+    parent_opt = Option("A_FAKE_OPTION", "default-value", mutable=True)
+    child_opt = Option("A_FAKE_OPTION", parent=parent_opt)
+    assert child_opt.mutable
+    assert child_opt.current == "default-value"
+
+    parent_opt.current = "new-value"
+    assert child_opt.current == "new-value"
+
+
+def test_option_parent_child_must_be_mutable():
+    mut_parent_opt = Option("A_FAKE_OPTION", "default-value", mutable=True)
+    immu_parent_opt = Option("A_FAKE_OPTION", "default-value", mutable=False)
+    with pytest.raises(TypeError, match="must be mutable"):
+        Option("A_FAKE_OPTION", parent=mut_parent_opt, mutable=False)
+    with pytest.raises(TypeError, match="must be mutable"):
+        Option("A_FAKE_OPTION", parent=immu_parent_opt, mutable=None)
+
+
+def test_no_default_or_parent():
+    with pytest.raises(
+        TypeError, match="Must specify either a default or a parent option"
+    ):
+        Option("A_FAKE_OPTION")
diff --git a/src/py/reactpy/tests/test_config.py b/src/py/reactpy/tests/test_config.py
index ecbbb998c..3428c3e28 100644
--- a/src/py/reactpy/tests/test_config.py
+++ b/src/py/reactpy/tests/test_config.py
@@ -27,3 +27,27 @@ def test_reactpy_debug_mode_toggle():
     # just check that nothing breaks
     config.REACTPY_DEBUG_MODE.current = True
     config.REACTPY_DEBUG_MODE.current = False
+
+
+def test_boolean():
+    assert config.boolean(True) is True
+    assert config.boolean(False) is False
+    assert config.boolean(1) is True
+    assert config.boolean(0) is False
+    assert config.boolean("true") is True
+    assert config.boolean("false") is False
+    assert config.boolean("True") is True
+    assert config.boolean("False") is False
+    assert config.boolean("TRUE") is True
+    assert config.boolean("FALSE") is False
+    assert config.boolean("1") is True
+    assert config.boolean("0") is False
+
+    with pytest.raises(ValueError):
+        config.boolean("2")
+
+    with pytest.raises(ValueError):
+        config.boolean("")
+
+    with pytest.raises(TypeError):
+        config.boolean(None)
diff --git a/src/py/reactpy/tests/test_core/test_vdom.py b/src/py/reactpy/tests/test_core/test_vdom.py
index 76e26e46f..37abad1d2 100644
--- a/src/py/reactpy/tests/test_core/test_vdom.py
+++ b/src/py/reactpy/tests/test_core/test_vdom.py
@@ -280,28 +280,36 @@ def test_invalid_vdom(value, error_message_pattern):
         validate_vdom_json(value)
 
 
-@pytest.mark.skipif(not REACTPY_DEBUG_MODE.current, reason="Only logs in debug mode")
-def test_debug_log_cannot_verify_keypath_for_genereators(caplog):
-    reactpy.vdom("div", (1 for i in range(10)))
-    assert len(caplog.records) == 1
-    assert caplog.records[0].message.startswith(
-        "Did not verify key-path integrity of children in generator"
-    )
-    caplog.records.clear()
-
+@pytest.mark.skipif(not REACTPY_DEBUG_MODE.current, reason="Only warns in debug mode")
+def test_warn_cannot_verify_keypath_for_genereators():
+    with pytest.warns(UserWarning) as record:
+        reactpy.vdom("div", (1 for i in range(10)))
+        assert len(record) == 1
+        assert (
+            record[0]
+            .message.args[0]
+            .startswith("Did not verify key-path integrity of children in generator")
+        )
 
-@pytest.mark.skipif(not REACTPY_DEBUG_MODE.current, reason="Only logs in debug mode")
-def test_debug_log_dynamic_children_must_have_keys(caplog):
-    reactpy.vdom("div", [reactpy.vdom("div")])
-    assert len(caplog.records) == 1
-    assert caplog.records[0].message.startswith("Key not specified for child")
 
-    caplog.records.clear()
+@pytest.mark.skipif(not REACTPY_DEBUG_MODE.current, reason="Only warns in debug mode")
+def test_warn_dynamic_children_must_have_keys():
+    with pytest.warns(UserWarning) as record:
+        reactpy.vdom("div", [reactpy.vdom("div")])
+        assert len(record) == 1
+        assert record[0].message.args[0].startswith("Key not specified for child")
 
     @reactpy.component
     def MyComponent():
         return reactpy.vdom("div")
 
-    reactpy.vdom("div", [MyComponent()])
-    assert len(caplog.records) == 1
-    assert caplog.records[0].message.startswith("Key not specified for child")
+    with pytest.warns(UserWarning) as record:
+        reactpy.vdom("div", [MyComponent()])
+        assert len(record) == 1
+        assert record[0].message.args[0].startswith("Key not specified for child")
+
+
+@pytest.mark.skipif(not REACTPY_DEBUG_MODE.current, reason="only checked in debug mode")
+def test_raise_for_non_json_attrs():
+    with pytest.raises(TypeError, match="JSON serializable"):
+        reactpy.html.div({"non_json_serializable_object": object()})
diff --git a/src/py/reactpy/tests/test_utils.py b/src/py/reactpy/tests/test_utils.py
index c71057f15..ca3080358 100644
--- a/src/py/reactpy/tests/test_utils.py
+++ b/src/py/reactpy/tests/test_utils.py
@@ -204,10 +204,6 @@ def test_del_html_body_transform():
             html.div(SOME_OBJECT),
             f"<div>{html_escape(str(SOME_OBJECT))}</div>",
         ),
-        (
-            html.div({"someAttribute": SOME_OBJECT}),
-            f'<div someattribute="{html_escape(str(SOME_OBJECT))}"></div>',
-        ),
         (
             html.div(
                 "hello", html.a({"href": "https://example.com"}, "example"), "world"

From bd60e6e7e1eec421e514cb0eff244bedc2268ca0 Mon Sep 17 00:00:00 2001
From: Mark Bakhit <16909269+Archmonger@users.noreply.github.com>
Date: Wed, 14 Jun 2023 23:15:37 -0700
Subject: [PATCH 34/37] Warn and fix missing mime types (#1050)

---
 docs/source/about/changelog.rst            |  4 ++++
 src/py/reactpy/reactpy/backend/__init__.py | 22 ++++++++++++++++++++++
 2 files changed, 26 insertions(+)

diff --git a/docs/source/about/changelog.rst b/docs/source/about/changelog.rst
index 62e7d47eb..a6eff8f73 100644
--- a/docs/source/about/changelog.rst
+++ b/docs/source/about/changelog.rst
@@ -23,6 +23,10 @@ more info, see the :ref:`Contributor Guide <Creating a Changelog Entry>`.
 Unreleased
 ----------
 
+**Changed**
+
+- :pull:`1050` - Warn and attempt to fix missing mime types, which can result in ``reactpy.run`` not working as expected.
+
 **Fixed**
 
 - :issue:`930` - better traceback for JSON serialization errors (via :pull:`1008`)
diff --git a/src/py/reactpy/reactpy/backend/__init__.py b/src/py/reactpy/reactpy/backend/__init__.py
index e69de29bb..e08e50649 100644
--- a/src/py/reactpy/reactpy/backend/__init__.py
+++ b/src/py/reactpy/reactpy/backend/__init__.py
@@ -0,0 +1,22 @@
+import mimetypes
+from logging import getLogger
+
+_logger = getLogger(__name__)
+
+# Fix for missing mime types due to OS corruption/misconfiguration
+# Example: https://github.com/encode/starlette/issues/829
+if not mimetypes.inited:
+    mimetypes.init()
+for extension, mime_type in {
+    ".js": "application/javascript",
+    ".css": "text/css",
+    ".json": "application/json",
+}.items():
+    if not mimetypes.types_map.get(extension):  # pragma: no cover
+        _logger.warning(
+            "Mime type '%s = %s' is missing. Please research how to "
+            "fix missing mime types on your operating system.",
+            extension,
+            mime_type,
+        )
+        mimetypes.add_type(mime_type, extension)

From 403e5f24536808218c511ed0ee0438fd6e1b94c8 Mon Sep 17 00:00:00 2001
From: Simone Rubino <daemo00@gmail.com>
Date: Fri, 16 Jun 2023 02:17:18 +0200
Subject: [PATCH 35/37] fix docs typos (#1061)

---
 docs/source/guides/adding-interactivity/index.rst             | 4 ++--
 .../adding-interactivity/responding-to-events/index.rst       | 2 +-
 .../guides/creating-interfaces/rendering-data/index.rst       | 2 +-
 3 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/docs/source/guides/adding-interactivity/index.rst b/docs/source/guides/adding-interactivity/index.rst
index b2beb3a81..af84fab88 100644
--- a/docs/source/guides/adding-interactivity/index.rst
+++ b/docs/source/guides/adding-interactivity/index.rst
@@ -58,13 +58,13 @@ Section 1: Responding to Events
 
 ReactPy lets you add event handlers to your parts of the interface. This means that you can
 define synchronous or asynchronous functions that are triggered when a particular user
-interaction occurs like clicking, hovering, of focusing on form inputs, and more.
+interaction occurs like clicking, hovering, focusing on form inputs, and more.
 
 .. reactpy:: responding-to-events/_examples/button_prints_message
 
 It may feel weird to define a function within a function like this, but doing so allows
 the ``handle_event`` function to access information from within the scope of the
-component. That's important if you want to use any arguments that may have beend passed
+component. That's important if you want to use any arguments that may have been passed
 your component in the handler.
 
 .. card::
diff --git a/docs/source/guides/adding-interactivity/responding-to-events/index.rst b/docs/source/guides/adding-interactivity/responding-to-events/index.rst
index 583a4a4b7..89f567ca0 100644
--- a/docs/source/guides/adding-interactivity/responding-to-events/index.rst
+++ b/docs/source/guides/adding-interactivity/responding-to-events/index.rst
@@ -29,7 +29,7 @@ To add an event handler to this button we'll do three things:
 
 It may feel weird to define a function within a function like this, but doing so allows
 the ``handle_event`` function to access information from within the scope of the
-component. That's important if you want to use any arguments that may have beend passed
+component. That's important if you want to use any arguments that may have been passed
 your component in the handler:
 
 .. reactpy:: _examples/button_prints_message
diff --git a/docs/source/guides/creating-interfaces/rendering-data/index.rst b/docs/source/guides/creating-interfaces/rendering-data/index.rst
index 8b11ac439..620426142 100644
--- a/docs/source/guides/creating-interfaces/rendering-data/index.rst
+++ b/docs/source/guides/creating-interfaces/rendering-data/index.rst
@@ -103,7 +103,7 @@ We could then add this code to our ``DataList`` component:
 
 .. warning::
 
-    The code below produces a bunch of warnings! Be sure to tead the
+    The code below produces a bunch of warnings! Be sure to read the
     :ref:`next section <Organizing Items With Keys>` to find out why.
 
 .. reactpy:: _examples/sorted_and_filtered_todo_list

From 6df7ecdebbf170b89902ff97caedd31a46bcadf1 Mon Sep 17 00:00:00 2001
From: Ryan Morshead <ryan.morshead@gmail.com>
Date: Fri, 16 Jun 2023 10:15:54 -0600
Subject: [PATCH 36/37] pre for 1.0.1 release (#1062)

---
 src/py/reactpy/reactpy/__init__.py | 2 +-
 tasks.py                           | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/py/reactpy/reactpy/__init__.py b/src/py/reactpy/reactpy/__init__.py
index 996a984b2..4fb4e8d09 100644
--- a/src/py/reactpy/reactpy/__init__.py
+++ b/src/py/reactpy/reactpy/__init__.py
@@ -21,7 +21,7 @@
 from reactpy.utils import Ref, html_to_vdom, vdom_to_html
 
 __author__ = "The Reactive Python Team"
-__version__ = "1.0.0"  # DO NOT MODIFY
+__version__ = "1.0.1"  # DO NOT MODIFY
 
 __all__ = [
     "backend",
diff --git a/tasks.py b/tasks.py
index e19476202..a36cd53ea 100644
--- a/tasks.py
+++ b/tasks.py
@@ -353,7 +353,7 @@ def parse_tag(tag: str) -> TagInfo:
         raise Exit(msg)
 
     version = match.group("version")
-    if not semver.Version.is_valid(version):
+    if not semver.VersionInfo.isvalid(version):
         raise Exit(f"Invalid version: {version} in tag {tag}")
 
     return TagInfo(tag=tag, name=match.group("name"), version=match.group("version"))

From 9bf3db7f6692fa26b0eb2e4cf159ac31b2925dfb Mon Sep 17 00:00:00 2001
From: Ryan Morshead <ryan.morshead@gmail.com>
Date: Fri, 16 Jun 2023 10:45:38 -0600
Subject: [PATCH 37/37] fix how we collect tags on current commit (#1063)

* fix how we collect tags on current commit

* fix dry-run handling
---
 tasks.py | 31 ++++++++++++-------------------
 1 file changed, 12 insertions(+), 19 deletions(-)

diff --git a/tasks.py b/tasks.py
index a36cd53ea..4bbfe52e2 100644
--- a/tasks.py
+++ b/tasks.py
@@ -209,10 +209,8 @@ def publish(context: Context, dry_run: str = ""):
         "js": prepare_js_release,
         "py": prepare_py_release,
     }
-
-    parsed_tags: list[TagInfo] = [
-        parse_tag(tag) for tag in dry_run.split(",") or get_current_tags(context)
-    ]
+    current_tags = dry_run.split(",") if dry_run else get_current_tags(context)
+    parsed_tags = [parse_tag(tag) for tag in current_tags]
 
     publishers: list[Callable[[bool], None]] = []
     for tag_info in parsed_tags:
@@ -315,23 +313,18 @@ def get_current_tags(context: Context) -> set[str]:
         context.run("git diff --cached --exit-code", hide=True)
         context.run("git diff --exit-code", hide=True)
     except Exception:
-        log.error("Cannot create a tag - there are uncommitted changes")
+        log.error("Cannot get current tags - there are uncommitted changes")
         return set()
 
-    tags_per_commit: dict[str, list[str]] = {}
-    for commit, tag in map(
-        str.split,
-        context.run(
-            r"git for-each-ref --format '%(objectname) %(refname:short)' refs/tags",
-            hide=True,
-        ).stdout.splitlines(),
-    ):
-        tags_per_commit.setdefault(commit, []).append(tag)
-
-    current_commit = context.run(
-        "git rev-parse HEAD", silent=True, external=True
-    ).stdout.strip()
-    tags = set(tags_per_commit.get(current_commit, set()))
+    # get tags for current commit
+    tags = {
+        line
+        for line in map(
+            str.strip,
+            context.run("git tag --points-at HEAD", hide=True).stdout.splitlines(),
+        )
+        if line
+    }
 
     if not tags:
         log.error("No tags found for current commit")
