diff --git a/CHANGELOG.md b/CHANGELOG.md
index bd572bcbe7fbfc695fda5281a68cba6664aee086..ae2ce734cb9ea00800c7b957a02d8eeb9b91ffc0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,11 @@
+# 2.2.5
+
+### Notable enhancements and fixes
+
+- Fixed timeslider not scrolling when the revision count is a multiple of 100
+- Added new Restful API for version 2 of Etherpad. It is available at /api-docs
+
+
 # 2.2.4
 
 ### Notable enhancements and fixes
diff --git a/admin/package.json b/admin/package.json
index aea97c9fd51897885b1ee20fc9af9f5af20a7cce..f0d52d2ac3d6dfbb39e77b13eeae58231b7b0356 100644
--- a/admin/package.json
+++ b/admin/package.json
@@ -1,7 +1,7 @@
 {
   "name": "admin",
   "private": true,
-  "version": "2.2.4",
+  "version": "2.2.5",
   "type": "module",
   "scripts": {
     "dev": "vite",
@@ -16,25 +16,25 @@
   "devDependencies": {
     "@radix-ui/react-dialog": "^1.1.1",
     "@radix-ui/react-toast": "^1.2.1",
-    "@types/react": "^18.3.5",
+    "@types/react": "^18.3.8",
     "@types/react-dom": "^18.2.25",
-    "@typescript-eslint/eslint-plugin": "^8.4.0",
-    "@typescript-eslint/parser": "^8.4.0",
+    "@typescript-eslint/eslint-plugin": "^8.6.0",
+    "@typescript-eslint/parser": "^8.6.0",
     "@vitejs/plugin-react-swc": "^3.5.0",
-    "eslint": "^9.9.1",
+    "eslint": "^9.10.0",
     "eslint-plugin-react-hooks": "^4.6.0",
-    "eslint-plugin-react-refresh": "^0.4.11",
-    "i18next": "^23.14.0",
+    "eslint-plugin-react-refresh": "^0.4.12",
+    "i18next": "^23.15.1",
     "i18next-browser-languagedetector": "^8.0.0",
-    "lucide-react": "^0.439.0",
+    "lucide-react": "^0.441.0",
     "react": "^18.2.0",
     "react-dom": "^18.2.0",
     "react-hook-form": "^7.53.0",
-    "react-i18next": "^15.0.1",
-    "react-router-dom": "^6.26.1",
+    "react-i18next": "^15.0.2",
+    "react-router-dom": "^6.26.2",
     "socket.io-client": "^4.7.5",
-    "typescript": "^5.5.4",
-    "vite": "^5.4.3",
+    "typescript": "^5.6.2",
+    "vite": "^5.4.7",
     "vite-plugin-static-copy": "^1.0.6",
     "vite-plugin-svgr": "^4.2.0",
     "zustand": "^4.5.5"
diff --git a/admin/public/ep_admin_pads/de.json b/admin/public/ep_admin_pads/de.json
index afb553caf46b1b9c5d7ca400e5bb1dff0b9d1070..67dd73ddf07ede4c295a00d7e32b4d34cabac9af 100644
--- a/admin/public/ep_admin_pads/de.json
+++ b/admin/public/ep_admin_pads/de.json
@@ -14,6 +14,7 @@
 	"ep_adminpads2_autoupdate.title": "Aktiviert oder deaktiviert automatische Aktualisierungen für die aktuelle Abfrage.",
 	"ep_adminpads2_confirm": "Willst du das Pad {{padID}} wirklich löschen?",
 	"ep_adminpads2_delete.value": "Löschen",
+  "ep_adminpads2_cleanup": "Historie aufräumen",
 	"ep_adminpads2_last-edited": "Zuletzt bearbeitet",
 	"ep_adminpads2_loading": "Lädt...",
 	"ep_adminpads2_manage-pads": "Pads verwalten",
diff --git a/admin/public/ep_admin_pads/en.json b/admin/public/ep_admin_pads/en.json
index 8a9044b1b84418879cc75d14dfa61669f59c4428..76354c6403f325db628fc5c83bc1ff1ccd842c60 100644
--- a/admin/public/ep_admin_pads/en.json
+++ b/admin/public/ep_admin_pads/en.json
@@ -4,6 +4,7 @@
   "ep_adminpads2_autoupdate.title": "Enables or disables automatic updates for the current query.",
   "ep_adminpads2_confirm": "Do you really want to delete the pad {{padID}}?",
   "ep_adminpads2_delete.value": "Delete",
+  "ep_adminpads2_cleanup": "Cleanup revisions",
   "ep_adminpads2_last-edited": "Last edited",
   "ep_adminpads2_loading": "Loading…",
   "ep_adminpads2_manage-pads": "Manage pads",
diff --git a/admin/src/App.tsx b/admin/src/App.tsx
index b3238ef9a73a22dfab0e4a13667142b192b7780e..708bd1bfd30154b8deb3aa1186fd30e2f3cb0325 100644
--- a/admin/src/App.tsx
+++ b/admin/src/App.tsx
@@ -1,4 +1,4 @@
-import {useEffect} from 'react'
+import {useEffect, useState} from 'react'
 import './App.css'
 import {connect} from 'socket.io-client'
 import {isJSONClean} from './utils/utils.ts'
@@ -6,107 +6,113 @@ import {NavLink, Outlet, useNavigate} from "react-router-dom";
 import {useStore} from "./store/store.ts";
 import {LoadingScreen} from "./utils/LoadingScreen.tsx";
 import {Trans, useTranslation} from "react-i18next";
-import {Cable, Construction, Crown, NotepadText, Wrench, PhoneCall} from "lucide-react";
+import {Cable, Construction, Crown, NotepadText, Wrench, PhoneCall, LucideMenu} from "lucide-react";
 
-const WS_URL = import.meta.env.DEV? 'http://localhost:9001' : ''
-export const App = ()=> {
-    const setSettings = useStore(state => state.setSettings);
-    const {t} = useTranslation()
-    const navigate = useNavigate()
+const WS_URL = import.meta.env.DEV ? 'http://localhost:9001' : ''
+export const App = () => {
+  const setSettings = useStore(state => state.setSettings);
+  const {t} = useTranslation()
+  const navigate = useNavigate()
+  const [sidebarOpen, setSidebarOpen] = useState<boolean>(true)
 
-    useEffect(() => {
-        fetch('/admin-auth/', {
-            method: 'POST'
-        }).then((value)=>{
-            if(!value.ok){
-                navigate('/login')
-            }
-        }).catch(()=>{
-            navigate('/login')
-        })
-    }, []);
+  useEffect(() => {
+    fetch('/admin-auth/', {
+      method: 'POST'
+    }).then((value) => {
+      if (!value.ok) {
+        navigate('/login')
+      }
+    }).catch(() => {
+      navigate('/login')
+    })
+  }, []);
 
-    useEffect(() => {
-        document.title = t('admin.page-title')
+  useEffect(() => {
+    document.title = t('admin.page-title')
 
-        useStore.getState().setShowLoading(true);
-        const settingSocket = connect(`${WS_URL}/settings`, {
-            transports: ['websocket'],
-        });
+    useStore.getState().setShowLoading(true);
+    const settingSocket = connect(`${WS_URL}/settings`, {
+      transports: ['websocket'],
+    });
 
-        const pluginsSocket = connect(`${WS_URL}/pluginfw/installer`, {
-          transports: ['websocket'],
-        })
+    const pluginsSocket = connect(`${WS_URL}/pluginfw/installer`, {
+      transports: ['websocket'],
+    })
 
-        pluginsSocket.on('connect', () => {
-            useStore.getState().setPluginsSocket(pluginsSocket);
-        });
+    pluginsSocket.on('connect', () => {
+      useStore.getState().setPluginsSocket(pluginsSocket);
+    });
 
 
-        settingSocket.on('connect', () => {
-            useStore.getState().setSettingsSocket(settingSocket);
-            useStore.getState().setShowLoading(false)
-            settingSocket.emit('load');
-            console.log('connected');
-        });
+    settingSocket.on('connect', () => {
+      useStore.getState().setSettingsSocket(settingSocket);
+      useStore.getState().setShowLoading(false)
+      settingSocket.emit('load');
+      console.log('connected');
+    });
 
-        settingSocket.on('disconnect', (reason) => {
-            // The settingSocket.io client will automatically try to reconnect for all reasons other than "io
-            // server disconnect".
-            useStore.getState().setShowLoading(true)
-            if (reason === 'io server disconnect') {
-                settingSocket.connect();
-            }
-        });
+    settingSocket.on('disconnect', (reason) => {
+      // The settingSocket.io client will automatically try to reconnect for all reasons other than "io
+      // server disconnect".
+      useStore.getState().setShowLoading(true)
+      if (reason === 'io server disconnect') {
+        settingSocket.connect();
+      }
+    });
 
-        settingSocket.on('settings', (settings) => {
-            /* Check whether the settings.json is authorized to be viewed */
-            if (settings.results === 'NOT_ALLOWED') {
-                console.log('Not allowed to view settings.json')
-                return;
-            }
+    settingSocket.on('settings', (settings) => {
+      /* Check whether the settings.json is authorized to be viewed */
+      if (settings.results === 'NOT_ALLOWED') {
+        console.log('Not allowed to view settings.json')
+        return;
+      }
 
-            /* Check to make sure the JSON is clean before proceeding */
-            if (isJSONClean(settings.results)) {
-                setSettings(settings.results);
-            } else {
-                alert('Invalid JSON');
-            }
-            useStore.getState().setShowLoading(false);
-        });
+      /* Check to make sure the JSON is clean before proceeding */
+      if (isJSONClean(settings.results)) {
+        setSettings(settings.results);
+      } else {
+        alert('Invalid JSON');
+      }
+      useStore.getState().setShowLoading(false);
+    });
 
-        settingSocket.on('saveprogress', (status)=>{
-            console.log(status)
-        })
+    settingSocket.on('saveprogress', (status) => {
+      console.log(status)
+    })
 
-        return () => {
-            settingSocket.disconnect();
-            pluginsSocket.disconnect()
-        }
-    }, []);
+    return () => {
+      settingSocket.disconnect();
+      pluginsSocket.disconnect()
+    }
+  }, []);
 
-    return <div id="wrapper">
-        <LoadingScreen/>
-        <div  className="menu">
-            <div className="inner-menu">
-                <span>
+  return <div id="wrapper" className={`${sidebarOpen ? '': 'closed' }`}>
+    <LoadingScreen/>
+    <div className="menu">
+      <div className="inner-menu">
+        <span>
                     <Crown width={40} height={40}/>
                     <h1>Etherpad</h1>
                 </span>
-                <ul>
-                    <li><NavLink to="/plugins"><Cable/><Trans i18nKey="admin_plugins"/></NavLink></li>
-                    <li><NavLink to={"/settings"}><Wrench/><Trans i18nKey="admin_settings"/></NavLink></li>
-                    <li><NavLink to={"/help"}> <Construction/> <Trans i18nKey="admin_plugins_info"/></NavLink></li>
-                    <li><NavLink to={"/pads"}><NotepadText/><Trans
-                        i18nKey="ep_admin_pads:ep_adminpads2_manage-pads"/></NavLink></li>
-                    <li><NavLink to={"/shout"}><PhoneCall/>Communication</NavLink></li>
-                </ul>
-            </div>
-        </div>
-        <div className="innerwrapper">
-            <Outlet/>
-        </div>
+        <ul onClick={()=>{
+          setSidebarOpen(false)
+        }}>
+          <li><NavLink to="/plugins"><Cable/><Trans i18nKey="admin_plugins"/></NavLink></li>
+          <li><NavLink to={"/settings"}><Wrench/><Trans i18nKey="admin_settings"/></NavLink></li>
+          <li><NavLink to={"/help"}> <Construction/> <Trans i18nKey="admin_plugins_info"/></NavLink></li>
+          <li><NavLink to={"/pads"}><NotepadText/><Trans
+            i18nKey="ep_admin_pads:ep_adminpads2_manage-pads"/></NavLink></li>
+          <li><NavLink to={"/shout"}><PhoneCall/>Communication</NavLink></li>
+        </ul>
+      </div>
     </div>
+      <button id="icon-button" onClick={() => {
+        setSidebarOpen(!sidebarOpen)
+      }}><LucideMenu/></button>
+    <div className="innerwrapper">
+      <Outlet/>
+    </div>
+  </div>
 }
 
 export default App
diff --git a/admin/src/index.css b/admin/src/index.css
index 99a406ee70d05b419beef5f0dc90a44f1cc4cccf..acc0d2e970af61719b91a9cb56b03621a957b9ee 100644
--- a/admin/src/index.css
+++ b/admin/src/index.css
@@ -1,282 +1,298 @@
 :root {
-    --etherpad-color: #0f775b;
-    --etherpad-comp: #9C8840;
-    --etherpad-light: #99FF99;
+  --etherpad-color: #0f775b;
+  --etherpad-comp: #9C8840;
+  --etherpad-light: #99FF99;
+  --sidebar-width: 20em;
 }
 
 @font-face {
-    font-family: Karla;
-    src: url(/Karla-Regular.ttf);
+  font-family: Karla;
+  src: url(/Karla-Regular.ttf);
 }
 
 html, body, #root {
-    box-sizing: border-box;
-    height: 100%;
-    font-family: "Karla", sans-serif;
+  box-sizing: border-box;
+  height: 100%;
+  font-family: "Karla", sans-serif;
 }
 
 *, *:before, *:after {
-    box-sizing: inherit;
-    font-size: 16px;
+  box-sizing: inherit;
+  font-size: 16px;
 }
 
 body {
-    margin: 0;
-    color: #333;
-    font: 14px helvetica, sans-serif;
-    background: #eee;
+  margin: 0;
+  color: #333;
+  font: 14px helvetica, sans-serif;
+  background: #eee;
 }
 
 div.menu {
-    height: 100vh;
-    font-size: 16px;
-    font-weight: bolder;
-    display: flex;
-    align-items: center;
-    justify-content: center;
-    max-width: 20%;
-    min-width: 20%;
-}
-
-.icon-button{
-    display: flex;
-    gap: 10px;
-    background-color: var(--etherpad-color);
-    color: white;
-    border: none;
-    padding: 10px 20px;
-    border-radius: 5px;
-    cursor: pointer;
+  left: 0;
+  transition: left .3s;
+  height: 100vh;
+  font-size: 16px;
+  font-weight: bolder;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: var(--sidebar-width);
+  z-index: 99;
+  position: fixed;
+}
+
+
+.icon-button {
+  display: flex;
+  gap: 10px;
+  background-color: var(--etherpad-color);
+  color: white;
+  border: none;
+  padding: 10px 20px;
+  border-radius: 5px;
+  cursor: pointer;
 }
 
 .icon-button svg {
-    align-self: center;
+  align-self: center;
 }
 
 .icon-button span {
-    align-self: center;
+  align-self: center;
 }
 
 
 div.menu span:first-child {
-    display: flex;
-    justify-content: center;
+  display: flex;
+  justify-content: center;
 }
 
 div.menu span:first-child svg {
-    margin-right: 10px;
-    align-self: center;
+  margin-right: 10px;
+  align-self: center;
 }
 
 
 div.menu h1 {
-    font-size: 50px;
-    text-align: center;
+  font-size: 50px;
+  text-align: center;
 }
 
 .inner-menu {
-    border-radius: 0 20px 20px 0;
-    padding: 10px;
-    flex-grow: 100;
-    background-color: var(--etherpad-comp);
-    color: white;
-    height: 100vh;
+  border-radius: 0 20px 20px 0;
+  padding: 10px;
+  flex-grow: 100;
+  background-color: var(--etherpad-comp);
+  color: white;
+  height: 100vh;
 }
 
 div.menu ul {
-    color: white;
-    padding: 0;
+  color: white;
+  padding: 0;
 }
 
 div.menu li a {
-    display: flex;
-    gap: 10px;
-    margin-bottom: 20px;
+  display: flex;
+  gap: 10px;
+  margin-bottom: 20px;
 }
 
 div.menu svg {
-    align-self: center;
+  align-self: center;
 }
 
 div.menu li {
-    padding: 10px;
-    color: white;
-    list-style: none;
-    margin-left: 3px;
-    line-height: 3;
+  padding: 10px;
+  color: white;
+  list-style: none;
+  margin-left: 3px;
+  line-height: 3;
 }
 
 
 div.menu li:has(.active) {
-    background-color: #9C885C ;
+  background-color: #9C885C;
 }
 
 div.menu li a {
-    color: lightgray;
+  color: lightgray;
 }
 
 
-
 div.innerwrapper {
-    background-color: #F0F0F0;
-    overflow: auto;
-    height: 100vh;
-    flex-grow: 100;
-    padding: 20px;
+  transition: margin-left .3s;
+  isolation: isolate;
+  background-color: #F0F0F0;
+  overflow: auto;
+  height: 100vh;
+  flex-grow: 100;
+  margin-left: var(--sidebar-width);
+  padding: 20px 20px 20px;
 }
 
 div.innerwrapper-err {
-    display: none;
+  display: none;
 }
 
 #wrapper {
-    display: flex;
-    background: none repeat scroll 0px 0px #FFFFFF;
-    box-shadow: 0px 1px 10px rgba(0, 0, 0, 0.2);
-    min-height: 100%;/*always display a scrollbar*/
-
+  background: none repeat scroll 0px 0px #FFFFFF;
+  box-shadow: 0px 1px 10px rgba(0, 0, 0, 0.2);
+  min-height: 100%; /*always display a scrollbar*/
 }
 
 h1 {
-    font-size: 29px;
+  font-size: 29px;
 }
 
 h2 {
-    font-size: 24px;
+  font-size: 24px;
 }
 
 .separator {
-    margin: 10px 0;
-    height: 1px;
-    background: #aaa;
-    background: -webkit-linear-gradient(left, #fff, #aaa 20%, #aaa 80%, #fff);
-    background: -moz-linear-gradient(left, #fff, #aaa 20%, #aaa 80%, #fff);
-    background: -ms-linear-gradient(left, #fff, #aaa 20%, #aaa 80%, #fff);
-    background: -o-linear-gradient(left, #fff, #aaa 20%, #aaa 80%, #fff);
+  margin: 10px 0;
+  height: 1px;
+  background: #aaa;
+  background: -webkit-linear-gradient(left, #fff, #aaa 20%, #aaa 80%, #fff);
+  background: -moz-linear-gradient(left, #fff, #aaa 20%, #aaa 80%, #fff);
+  background: -ms-linear-gradient(left, #fff, #aaa 20%, #aaa 80%, #fff);
+  background: -o-linear-gradient(left, #fff, #aaa 20%, #aaa 80%, #fff);
 }
 
 form {
-    margin-bottom: 0;
+  margin-bottom: 0;
 }
 
 #inner {
-    width: 300px;
-    margin: 0 auto;
+  width: 300px;
+  margin: 0 auto;
 }
 
 input {
-    font-weight: bold;
-    font-size: 15px;
+  font-weight: bold;
+  font-size: 15px;
 }
 
 
 .sort {
-    cursor: pointer;
+  cursor: pointer;
 }
+
 .sort:after {
-    content: '▲▼'
+  content: '▲▼'
 }
+
 .sort.up:after {
-    content:'▲'
+  content: '▲'
 }
+
 .sort.down:after {
-    content:'▼'
+  content: '▼'
 }
 
 
 #installed-plugins thead tr th:nth-child(3) {
-    width: 15%;
+  width: 15%;
 }
 
 table {
-    border: 1px solid #ddd;
-    border-radius: 3px;
-    border-spacing: 0;
-    width: 100%;
-    margin: 20px 0;
+  border: 1px solid #ddd;
+  border-radius: 3px;
+  border-spacing: 0;
+  width: 100%;
+  margin: 20px 0;
 }
 
+.table-container {
+  width: 100%;
+  overflow: auto;
+  max-height: 90vh;
+}
 
 
-
-
-#available-plugins  th:first-child, #available-plugins  th:nth-child(2){
-    text-align: center;
+#available-plugins th:first-child, #available-plugins th:nth-child(2) {
+  text-align: center;
 }
 
 td, th {
-    padding: 5px;
+  padding: 5px;
 }
 
 .template {
-    display: none;
+  display: none;
 }
 
-#installed-plugins td>div {
-    position: relative;/* Allows us to position the loading indicator relative to this row */
-    display: inline-block; /*make this fill the whole cell*/
-    width:100%;
+#installed-plugins td > div {
+  position: relative; /* Allows us to position the loading indicator relative to this row */
+  display: inline-block; /*make this fill the whole cell*/
+  width: 100%;
 }
 
 .messages {
-    height: 5em;
+  height: 5em;
 }
+
 .messages * {
-    display: none;
-    text-align: center;
+  display: none;
+  text-align: center;
 }
+
 .messages .fetching {
-    display: block;
+  display: block;
 }
 
 .progress {
-    position: absolute;
-    top: 0; left: 0; bottom:0; right:0;
-    padding: auto;
+  position: absolute;
+  top: 0;
+  left: 0;
+  bottom: 0;
+  right: 0;
+  padding: auto;
 
-    background: rgb(255,255,255);
-    display: none;
+  background: rgb(255, 255, 255);
+  display: none;
 }
 
 #search-progress.progress {
-    padding-top: 20%;
-    background: rgba(255,255,255,0.3);
+  padding-top: 20%;
+  background: rgba(255, 255, 255, 0.3);
 }
 
 .progress * {
-    display: block;
-    margin: 0 auto;
-    text-align: center;
-    color: #666;
+  display: block;
+  margin: 0 auto;
+  text-align: center;
+  color: #666;
 }
 
 
 .settings-page {
-    display: flex;
-    flex-direction: column;
-    gap: 20px;
-    height: 100%;
+  display: flex;
+  flex-direction: column;
+  gap: 20px;
+  height: 100%;
 }
 
 .settings {
-    flex-grow: max(1, 1);
-    outline: none;
-    width: 100%;
-    resize: none;
-    font-family: monospace;
+  flex-grow: max(1, 1);
+  outline: none;
+  width: 100%;
+  resize: none;
+  font-family: monospace;
 }
 
 #response {
-    display: inline;
+  display: inline;
 }
 
 a:link, a:visited, a:hover, a:focus {
-    color: #333333;
-    text-decoration: none;
+  color: #333333;
+  text-decoration: none;
 }
 
 a:focus, a:hover {
-    text-decoration: underline;
+  text-decoration: underline;
 }
 
 .installed-results a:link,
@@ -286,524 +302,565 @@ a:focus, a:hover {
 .installed-results a:hover,
 .search-results a:hover,
 .installed-results a:focus,
-.search-results a:focus  {
-    text-decoration: underline;
+.search-results a:focus {
+  text-decoration: underline;
 }
 
 .installed-results a:focus,
 .search-results a:focus,
 .installed-results a:hover,
 .search-results a:hover {
-    text-decoration: none;
+  text-decoration: none;
 }
 
 pre {
-    white-space: pre-wrap;
-    word-wrap: break-word;
+  white-space: pre-wrap;
+  word-wrap: break-word;
+}
+
+
+#icon-button {
+  color: var(--etherpad-color);
+  top: 10px;
+  background-color: transparent;
+  border: none;
+  z-index: 99;
+  position: absolute;
+  left: 10px;
+}
+
+
+.inner-menu span:nth-child(2) {
+  display: flex;
+  margin-top: 30px;
+}
+
+#wrapper.closed .menu {
+  left: calc(-1 * var(--sidebar-width));
+}
+
+#wrapper.closed .innerwrapper {
+  margin-left: 0;
 }
 
 @media (max-width: 800px) {
-    div.innerwrapper {
-        padding: 0 15px 15px 15px;
-    }
-
-    div.menu {
-        padding: 1px 15px 0 15px;
-        position: static;
-        height: auto;
-        border-right: none;
-        width: auto;
-        float: left;
-    }
-
-    table {
-        border: none;
-    }
-
-    table, thead, tbody, td, tr {
-        display: block;
-    }
-
-    thead tr {
-        display: none;
-    }
-
-    tr {
-        border: 1px solid #ccc;
-        margin-bottom: 5px;
-        border-radius: 3px;
-    }
-
-    td {
-        border: none;
-        border-bottom: 1px solid #eee;
-        position: relative;
-        padding-left: 50%;
-        white-space: normal;
-        text-align: left;
-    }
-
-    td.name {
-        word-wrap: break-word;
-    }
-
-    td:before {
-        position: absolute;
-        top: 6px;
-        left: 6px;
-        text-align: left;
-        padding-right: 10px;
-        white-space: nowrap;
-        font-weight: bold;
-        content: attr(data-label);
-    }
-
-    td:last-child {
-        border-bottom: none;
-    }
-
-    table input[type="button"] {
-        float: none;
-    }
+
+  div.innerwrapper {
+    margin-left: 0;
+  }
+
+  .inner-menu {
+    border-radius: 0;
+  }
+
+  div.menu {
+    height: auto;
+    border-right: none;
+    --sidebar-width: 100%;
+    float: left;
+  }
+
+  table {
+    border: none;
+  }
+
+  table, thead, tbody, td, tr {
+    display: block;
+  }
+
+  thead tr {
+    display: none;
+  }
+
+  tr {
+    border: 1px solid #ccc;
+    margin-bottom: 5px;
+    border-radius: 3px;
+  }
+
+  td {
+    border: none;
+    border-bottom: 1px solid #eee;
+    position: relative;
+    padding-left: 50%;
+    white-space: normal;
+    text-align: left;
+  }
+
+  td.name {
+    word-wrap: break-word;
+  }
+
+  td:before {
+    position: absolute;
+    top: 6px;
+    left: 6px;
+    text-align: left;
+    padding-right: 10px;
+    white-space: nowrap;
+    font-weight: bold;
+    content: attr(data-label);
+  }
+
+  td:last-child {
+    border-bottom: none;
+  }
+
+  table input[type="button"] {
+    float: none;
+  }
 }
 
 
 .settings-button-bar {
-    margin-top: 10px;
-    display: flex;
-    gap: 10px;
+  margin-top: 10px;
+  display: flex;
+  gap: 10px;
 }
 
 .login-background {
-    background-image: url("/fond.jpg");
-    background-repeat: no-repeat;
-    background-size: cover;
-    display: flex;
-    justify-content: center;
-    align-items: center;
-    height: 100vh;
-    background-color: #f0f0f0;
+  background-image: url("/fond.jpg");
+  background-position: center;
+  background-repeat: no-repeat;
+  background-size: cover;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  height: 100vh;
+  background-color: #f0f0f0;
 }
 
 .login-inner-box div {
-    margin-top: 1rem;
+  margin-top: 1rem;
 }
 
-.login-inner-box [type=submit]{
-    margin-top: 2rem;
+.login-inner-box [type=submit] {
+  margin-top: 2rem;
 }
 
 
-
 .login-textinput {
-    width: 100%;
-    padding: 10px;
-    background-color: #fffacc;
-    border-radius: 5px;
-    border: 1px solid #ccc;
-    margin-bottom: 10px;
+  width: 100%;
+  padding: 10px;
+  background-color: #fffacc;
+  border-radius: 5px;
+  border: 1px solid #ccc;
+  margin-bottom: 10px;
 }
 
 .login-box {
-    width: 20%;
-    padding: 20px;
-    border-radius: 40px;
-    box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
-    background-color: #fff;
+  padding: 20px;
+  border-radius: 40px;
+  box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
+  background-color: #fff;
 }
 
-.login-inner-box{
-    position: relative;
-    padding: 20px;
+
+@media (max-width: 900px) {
+  .login-box {
+    width: 90%
+  }
+}
+
+.login-inner-box {
+  position: relative;
+  padding: 20px;
 }
 
 .login-title {
-    padding: 0;
-    margin: 0;
-    text-align: center;
-    color: var(--etherpad-color);
-    font-size: 4rem;
-    font-weight: 1000;
+  padding: 0;
+  margin: 0;
+  text-align: center;
+  color: var(--etherpad-color);
+  font-size: 4rem;
+  font-weight: 1000;
 }
 
 .login-button {
-    padding: 10px;
-    background-color: var(--etherpad-color);
-    color: white;
-    border: none;
-    border-radius: 5px;
-    cursor: pointer;
-    width: 100%;
-    height: 40px;
+  padding: 10px;
+  background-color: var(--etherpad-color);
+  color: white;
+  border: none;
+  border-radius: 5px;
+  cursor: pointer;
+  width: 100%;
+  height: 40px;
 }
 
 .dialog-overlay {
-    position: fixed;
-    inset: 0;
-    background-color: white;
-    z-index: 100;
+  position: fixed;
+  inset: 0;
+  background-color: white;
+  z-index: 100;
 }
 
 
 .dialog-confirm-overlay {
-    position: fixed;
-    inset: 0;
-    background-color: rgba(0, 0, 0, 0.5);
-    z-index: 100;
+  position: fixed;
+  inset: 0;
+  background-color: rgba(0, 0, 0, 0.5);
+  z-index: 100;
 }
 
 
 .dialog-confirm-content {
-    position: fixed;
-    top: 50%;
-    left: 50%;
-    background-color: white;
-    transform: translate(-50%, -50%);
-    padding: 20px;
-    z-index: 101;
+  position: fixed;
+  top: 50%;
+  left: 50%;
+  background-color: white;
+  transform: translate(-50%, -50%);
+  padding: 20px;
+  z-index: 101;
 }
 
 
 .dialog-content {
-    position: fixed;
-    top: 50%;
-    left: 50%;
-    transform: translate(-50%, -50%);
-    padding: 20px;
-    z-index: 101;
+  position: fixed;
+  top: 50%;
+  left: 50%;
+  transform: translate(-50%, -50%);
+  padding: 20px;
+  z-index: 101;
 }
 
 .dialog-title {
-    color: var(--etherpad-color);
-    font-size: 2em;
-    margin-bottom: 20px;
+  color: var(--etherpad-color);
+  font-size: 2em;
+  margin-bottom: 20px;
 }
 
 
-
 .ToastViewport {
-    position: fixed;
-    top: 10px;
-    right: 20px;
-    display: flex;
-    flex-direction: column;
-    gap: 10px;
-    width: 390px;
-    max-width: 100vw;
-    margin: 0;
-    list-style: none;
-    z-index: 2147483647;
-    outline: none;
+  position: fixed;
+  top: 10px;
+  right: 20px;
+  display: flex;
+  flex-direction: column;
+  gap: 10px;
+  width: 390px;
+  max-width: 100vw;
+  margin: 0;
+  list-style: none;
+  z-index: 2147483647;
+  outline: none;
 }
 
 .ToastRootSuccess {
-    background-color: lawngreen;
+  background-color: lawngreen;
 }
 
 .ToastRootFailure {
-    background-color: red;
+  background-color: red;
 }
 
 .ToastRootFailure > .ToastTitle {
-    color: white;
+  color: white;
 }
 
 .ToastRoot {
-    border-radius: 20px;
-    box-shadow: hsl(206 22% 7% / 35%) 0px 10px 38px -10px, hsl(206 22% 7% / 20%) 0px 10px 20px -15px;
-    padding: 15px;
-    display: grid;
-    grid-template-areas: 'title action' 'description action';
-    grid-template-columns: auto max-content;
-    column-gap: 15px;
-    align-items: center;
+  border-radius: 20px;
+  box-shadow: hsl(206 22% 7% / 35%) 0px 10px 38px -10px, hsl(206 22% 7% / 20%) 0px 10px 20px -15px;
+  padding: 15px;
+  display: grid;
+  grid-template-areas: 'title action' 'description action';
+  grid-template-columns: auto max-content;
+  column-gap: 15px;
+  align-items: center;
 }
+
 .ToastRoot[data-state='open'] {
-    animation: slideIn 150ms cubic-bezier(0.16, 1, 0.3, 1);
+  animation: slideIn 150ms cubic-bezier(0.16, 1, 0.3, 1);
 }
+
 .ToastRoot[data-state='closed'] {
-    animation: hide 100ms ease-in;
+  animation: hide 100ms ease-in;
 }
+
 .ToastRoot[data-swipe='move'] {
-    transform: translateX(var(--radix-toast-swipe-move-x));
+  transform: translateX(var(--radix-toast-swipe-move-x));
 }
+
 .ToastRoot[data-swipe='cancel'] {
-    transform: translateX(0);
-    transition: transform 200ms ease-out;
+  transform: translateX(0);
+  transition: transform 200ms ease-out;
 }
+
 .ToastRoot[data-swipe='end'] {
-    animation: swipeOut 100ms ease-out;
+  animation: swipeOut 100ms ease-out;
 }
 
 @keyframes hide {
-    from {
-        opacity: 1;
-    }
-    to {
-        opacity: 0;
-    }
+  from {
+    opacity: 1;
+  }
+  to {
+    opacity: 0;
+  }
 }
 
 @keyframes slideIn {
-    from {
-        transform: translateX(calc(100% + var(--viewport-padding)));
-    }
-    to {
-        transform: translateX(0);
-    }
+  from {
+    transform: translateX(calc(100% + var(--viewport-padding)));
+  }
+  to {
+    transform: translateX(0);
+  }
 }
 
 @keyframes swipeOut {
-    from {
-        transform: translateX(var(--radix-toast-swipe-end-x));
-    }
-    to {
-        transform: translateX(calc(100% + var(--viewport-padding)));
-    }
+  from {
+    transform: translateX(var(--radix-toast-swipe-end-x));
+  }
+  to {
+    transform: translateX(calc(100% + var(--viewport-padding)));
+  }
 }
 
 .ToastTitle {
-    grid-area: title;
-    margin-bottom: 5px;
-    font-weight: 500;
-    color: var(--slate-12);
-    padding: 10px;
-    font-size: 15px;
+  grid-area: title;
+  margin-bottom: 5px;
+  font-weight: 500;
+  color: var(--slate-12);
+  padding: 10px;
+  font-size: 15px;
 }
 
 .ToastDescription {
-    grid-area: description;
-    margin: 0;
-    color: var(--slate-11);
-    font-size: 13px;
-    line-height: 1.3;
+  grid-area: description;
+  margin: 0;
+  color: var(--slate-11);
+  font-size: 13px;
+  line-height: 1.3;
 }
 
 .ToastAction {
-    grid-area: action;
+  grid-area: action;
 }
 
 .help-block {
-    display: grid;
-    grid-template-columns: repeat(2, minmax(0, 1fr));
-    gap: 20px
+  display: grid;
+  grid-template-columns: repeat(2, minmax(0, 1fr));
+  gap: 20px
 }
 
 .search-field {
-    position: relative;
+  position: relative;
 }
 
 .search-field input {
-    border-color: transparent;
-    border-radius: 20px;
-    height: 2.5rem;
-    width: 100vh;
-    padding: 5px 5px 5px 30px;
+  border-color: transparent;
+  border-radius: 20px;
+  height: 2.5rem;
+  width: 100%;
+  padding: 5px 5px 5px 30px;
 }
 
 .search-field input:focus {
-    outline: none;
+  outline: none;
 }
 
 
 .send-message {
-    position: relative;
+  position: relative;
 }
 
 .send-message input {
-    width: auto;
+  width: auto;
 }
 
 .send-message {
 }
 
 .send-message svg {
-    position: absolute;
-    right: 3px;
-    bottom: -3px;
-    left: auto !important;
+  position: absolute;
+  right: 3px;
+  bottom: -3px;
+  left: auto !important;
 }
 
 .search-field svg {
-    position: absolute;
-    left: 3px;
-    bottom: -3px;
+  position: absolute;
+  left: 3px;
+  bottom: -3px;
 }
 
 
 .search-field svg {
-    color: gray
+  color: gray
 }
 
 table {
-    margin: 25px 0;
-    font-size: 0.9em;
-    font-family: sans-serif;
-    min-width: 400px;
-    box-shadow: 0 0 20px rgba(0, 0, 0, 0.15);
+  margin: 25px 0;
+  font-size: 0.9em;
+  font-family: sans-serif;
+  min-width: 400px;
+  box-shadow: 0 0 20px rgba(0, 0, 0, 0.15);
 }
 
 th:first-child {
-    border-top-left-radius: 10px;
+  border-top-left-radius: 10px;
 }
 
 th:last-child {
-    border-top-right-radius: 10px;
+  border-top-right-radius: 10px;
 }
 
 table thead tr {
-    font-size: 25px;
-    background-color: var(--etherpad-color);
-    color: #ffffff;
-    text-align: left;
+  font-size: 25px;
+  background-color: var(--etherpad-color);
+  color: #ffffff;
+  text-align: left;
 }
 
 table tbody tr {
-    border-bottom: 1px solid #dddddd;
+  border-bottom: 1px solid #dddddd;
 }
 
 table tr:nth-child(even) td {
-    background-color: lightgray;
+  background-color: lightgray;
 }
 
 table tr td {
-    padding: 12px 15px;
+  padding: 12px 15px;
 }
 
 table tbody tr:nth-of-type(even) {
-    background-color: #f3f3f3;
+  background-color: #f3f3f3;
 }
 
 table tbody tr:last-of-type {
-    border-bottom: 2px solid #009879;
+  border-bottom: 2px solid #009879;
 }
 
 table tbody tr.active-row {
-    font-weight: bold;
-    color: #009879;
+  font-weight: bold;
+  color: #009879;
 }
 
 
-.pad-pagination{
-    display: flex;
-    justify-content: center;
-    gap: 10px;
-    margin-top: 20px;
+.pad-pagination {
+  display: flex;
+  justify-content: center;
+  gap: 10px;
+  margin-top: 20px;
 }
 
 .pad-pagination button {
-    display: flex;
-    padding: 10px 20px;
-    border-radius: 5px;
-    border: none;
-    color: black;
-    cursor: pointer;
+  display: flex;
+  padding: 10px 20px;
+  border-radius: 5px;
+  border: none;
+  color: black;
+  cursor: pointer;
 }
 
 
 .pad-pagination button:disabled {
-    background: transparent;
-    color: lightgrey;
-    cursor: not-allowed;
+  background: transparent;
+  color: lightgrey;
+  cursor: not-allowed;
 }
 
 .pad-pagination span {
-    align-self: center;
+  align-self: center;
 }
 
-.pad-pagination >span {
-    font-size: 20px;
+.pad-pagination > span {
+  font-size: 20px;
 }
 
 
 .login-page .login-form .input-control input[type=text], .login-page .login-form .input-control input[type=email], .login-page .login-form .input-control input[type=password], .login-page .signup-form .input-control input[type=text], .login-page .signup-form .input-control input[type=email], .login-page .signup-form .input-control input[type=password], .login-page .forgot-form .input-control input[type=text], .login-page .forgot-form .input-control input[type=email], .login-page .forgot-form .input-control input[type=password] {
-    width: 100%;
-    padding: 12px 20px;
-    margin: 8px 0;
-    display: inline-block;
-    border-bottom: 2px solid #ccc;
-    border-top: 0;
-    border-left: 0;
-    border-right: 0;
-    -webkit-box-sizing: border-box;
-    box-sizing: border-box;
-    border-radius: 5px;
-    font-size: 14px;
-    color: #666;
-    background-color: #f8f8f8;
-    -webkit-transition: all 0.3s ease-in-out;
-    transition: all 0.3s ease-in-out;
+  width: 100%;
+  padding: 12px 20px;
+  margin: 8px 0;
+  display: inline-block;
+  border-bottom: 2px solid #ccc;
+  border-top: 0;
+  border-left: 0;
+  border-right: 0;
+  -webkit-box-sizing: border-box;
+  box-sizing: border-box;
+  border-radius: 5px;
+  font-size: 14px;
+  color: #666;
+  background-color: #f8f8f8;
+  -webkit-transition: all 0.3s ease-in-out;
+  transition: all 0.3s ease-in-out;
 }
 
 input, button, select, optgroup, textarea {
-    margin: 0;
-    font-family: inherit;
-    font-size: inherit;
-    line-height: inherit;
+  margin: 0;
+  font-family: inherit;
+  font-size: inherit;
+  line-height: inherit;
 }
 
 .icon-input {
-    position: relative;
+  position: relative;
 }
 
 .icon-input svg {
-    position: absolute;
-    top: 50%;
-    transform: translateY(-50%);
-    right: 10px;
-    color: #666;
+  position: absolute;
+  top: 50%;
+  transform: translateY(-50%);
+  right: 10px;
+  color: #666;
 }
 
 
 .SwitchRoot {
-    align-self: center;
-    width: 60px;
-    height: 30px;
-    background-color: black;
-    border-radius: 9999px;
-    position: relative;
-    box-shadow: 0 2px 10px var(--black-a7);
-    -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
+  align-self: center;
+  width: 60px;
+  height: 30px;
+  background-color: black;
+  border-radius: 9999px;
+  position: relative;
+  box-shadow: 0 2px 10px var(--black-a7);
+  -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
 }
+
 .SwitchRoot:focus {
-    box-shadow: 0 0 0 2px black;
+  box-shadow: 0 0 0 2px black;
 }
+
 .SwitchRoot[data-state='checked'] {
-    background-color: var(--etherpad-color);
+  background-color: var(--etherpad-color);
 }
 
 .SwitchThumb {
-    display: block;
-    width: 20px;
-    height: 20px;
-    background-color: white;
-    border-radius: 9999px;
-    box-shadow: 0 2px 2px var(--black-a7);
-    transition: transform 100ms;
-    transform: translateX(2px);
-    will-change: transform;
+  display: block;
+  width: 20px;
+  height: 20px;
+  background-color: white;
+  border-radius: 9999px;
+  box-shadow: 0 2px 2px var(--black-a7);
+  transition: transform 100ms;
+  transform: translateX(2px);
+  will-change: transform;
 }
+
 .SwitchThumb[data-state='checked'] {
-    transform: translateX(25px);
+  transform: translateX(25px);
 }
 
 .Label {
-    color: white;
-    font-size: 15px;
-    line-height: 1;
+  color: white;
+  font-size: 15px;
+  line-height: 1;
 }
 
 .message {
-    position: relative;
-    padding: 10px;
-    border: 1px solid #e0e0e0;
-    margin: 10px 20px 10px 10px;
-    border-radius: 10px 0 10px 10px;
-    background-color: var(--etherpad-color);
-    color: white
+  position: relative;
+  padding: 10px;
+  border: 1px solid #e0e0e0;
+  margin: 10px 20px 10px 10px;
+  border-radius: 10px 0 10px 10px;
+  background-color: var(--etherpad-color);
+  color: white
 }
 
-.search-pads{
+.search-pads {
   text-align: center;
 }
 
diff --git a/admin/src/pages/HomePage.tsx b/admin/src/pages/HomePage.tsx
index c0d5913eefa42a1bf8d8b7b29c84936eadb38d65..589e40e5e65bb75cde0be9a0a12f7d3d958b3e77 100644
--- a/admin/src/pages/HomePage.tsx
+++ b/admin/src/pages/HomePage.tsx
@@ -193,6 +193,7 @@ export const HomePage = () => {
         <h2><Trans i18nKey="admin_plugins.available"/></h2>
         <SearchField onChange={v=>{setSearchTerm(v.target.value)}} placeholder={t('admin_plugins.available_search.placeholder')} value={searchTerm}/>
 
+      <div className="table-container">
         <table id="available-plugins">
             <thead>
             <tr>
@@ -240,5 +241,6 @@ export const HomePage = () => {
             }
             </tbody>
         </table>
+      </div>
     </div>
 }
diff --git a/admin/src/pages/PadPage.tsx b/admin/src/pages/PadPage.tsx
index e663603cdd915be7c25ec74b7007fad510c48a56..b5db854f567f19f6c1e142aef5b88ca557cda1c3 100644
--- a/admin/src/pages/PadPage.tsx
+++ b/admin/src/pages/PadPage.tsx
@@ -6,7 +6,7 @@ import {useDebounce} from "../utils/useDebounce.ts";
 import {determineSorting} from "../utils/sorting.ts";
 import * as Dialog from "@radix-ui/react-dialog";
 import {IconButton} from "../components/IconButton.tsx";
-import {ChevronLeft, ChevronRight, Eye, Trash2} from "lucide-react";
+import {ChevronLeft, ChevronRight, Eye, Trash2, FileStack} from "lucide-react";
 import {SearchField} from "../components/SearchField.tsx";
 
 export const PadPage = ()=>{
@@ -23,6 +23,7 @@ export const PadPage = ()=>{
     const pads = useStore(state=>state.pads)
     const [currentPage, setCurrentPage] = useState<number>(0)
     const [deleteDialog, setDeleteDialog] = useState<boolean>(false)
+    const [errorText, setErrorText] = useState<string|null>(null)
     const [padToDelete, setPadToDelete] = useState<string>('')
     const pages = useMemo(()=>{
         if(!pads){
@@ -68,12 +69,35 @@ export const PadPage = ()=>{
                 results: newPads
             })
         })
+
+        settingsSocket.on('results:cleanupPadRevisions', (data)=>{
+          let newPads = useStore.getState().pads?.results ?? []
+
+          if (data.error) {
+            setErrorText(data.error)
+            return
+          }
+
+          newPads.forEach((pad)=>{
+            if (pad.padName === data.padId) {
+              pad.revisionNumber = data.keepRevisions
+            }
+          })
+
+          useStore.getState().setPads({
+            results: newPads,
+            total: useStore.getState().pads!.total
+          })
+        })
     }, [settingsSocket, pads]);
 
     const deletePad = (padID: string)=>{
         settingsSocket?.emit('deletePad', padID)
     }
 
+    const cleanupPad = (padID: string)=>{
+        settingsSocket?.emit('cleanupPadRevisions', padID)
+    }
 
 
     return <div>
@@ -100,6 +124,21 @@ export const PadPage = ()=>{
             </Dialog.Content>
         </Dialog.Portal>
         </Dialog.Root>
+        <Dialog.Root open={errorText !== null}>
+          <Dialog.Portal>
+            <Dialog.Overlay className="dialog-confirm-overlay"/>
+            <Dialog.Content className="dialog-confirm-content">
+              <div>
+                <div>Error occured: {errorText}</div>
+                <div className="settings-button-bar">
+                  <button onClick={() => {
+                    setErrorText(null)
+                  }}>OK</button>
+                </div>
+              </div>
+            </Dialog.Content>
+          </Dialog.Portal>
+        </Dialog.Root>
         <h1><Trans i18nKey="ep_admin_pads:ep_adminpads2_manage-pads"/></h1>
         <SearchField value={searchTerm} onChange={v=>setSearchTerm(v.target.value)} placeholder={t('ep_admin_pads:ep_adminpads2_search-heading')}/>
         <table>
@@ -150,6 +189,9 @@ export const PadPage = ()=>{
                                     setPadToDelete(pad.padName)
                                     setDeleteDialog(true)
                                 }}/>
+                                <IconButton icon={<FileStack/>} title={<Trans i18nKey="ep_admin_pads:ep_adminpads2_cleanup"/>} onClick={()=>{
+                                  cleanupPad(pad.padName)
+                                }}/>
                                 <IconButton icon={<Eye/>} title="view" onClick={()=>window.open(`/p/${pad.padName}`, '_blank')}/>
                             </div>
                         </td>
diff --git a/bin/make_docs.ts b/bin/make_docs.ts
index d414822dd6f2fa588241ef3cbfdbb09e43a454ca..d4abfc97df5c6c98e8051c76f9b5c0f9a0f35b05 100644
--- a/bin/make_docs.ts
+++ b/bin/make_docs.ts
@@ -57,7 +57,7 @@ createDirIfNotExists('../out/doc/api')
 
 
 
-exec(`asciidoctor -D ../out/doc ../doc/index.adoc */**.adoc -a VERSION=${VERSION}`)
+exec(`asciidoctor -D ../out/doc ../doc/index.adoc ../*/**.adoc -a VERSION=${VERSION}`)
 exec(`asciidoctor -D ../out/doc/api  ../doc/api/*.adoc -a VERSION=${VERSION}`)
 
 copyFolderSync('../doc/public/', '../out/doc/')
diff --git a/bin/package.json b/bin/package.json
index acea680ffeafb9c20e86ab53574767dd4b5645e7..347901819a54ee42510297d910d25ba2f403d5fe 100644
--- a/bin/package.json
+++ b/bin/package.json
@@ -1,6 +1,6 @@
 {
   "name": "bin",
-  "version": "2.2.4",
+  "version": "2.2.5",
   "description": "",
   "main": "checkAllPads.js",
   "directories": {
@@ -11,13 +11,13 @@
     "ep_etherpad-lite": "workspace:../src",
     "log4js": "^6.9.1",
     "semver": "^7.6.3",
-    "tsx": "^4.19.0",
-    "ueberdb2": "^4.2.103"
+    "tsx": "^4.19.1",
+    "ueberdb2": "^5.0.2"
   },
   "devDependencies": {
-    "@types/node": "^22.5.4",
+    "@types/node": "^22.5.5",
     "@types/semver": "^7.5.8",
-    "typescript": "^5.5.4"
+    "typescript": "^5.6.2"
   },
   "scripts": {
     "makeDocs": "node --import tsx make_docs.ts",
diff --git a/doc/.vitepress/config.mts b/doc/.vitepress/config.mts
index 170f21898328990e16d2b0c5c072fc1497e37dc7..c96fa3bd363cd22eb0a885d7c36a91b3aeaa47c2 100644
--- a/doc/.vitepress/config.mts
+++ b/doc/.vitepress/config.mts
@@ -32,6 +32,7 @@ export default defineConfig({
                 { text: 'Stats', link: '/stats.md' },
                 {text: 'Skins', link: '/skins.md' },
                 {text: 'Demo', link: '/demo.md' },
+              {text: 'CLI', link: '/cli.md'},
                 ]
           },
           {
diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml
index 62b9bc56366d040ea795649027d328b683cdc22f..b8c01f26325e6884395a5f86c2527f8e958102d1 100644
--- a/docker-compose.dev.yml
+++ b/docker-compose.dev.yml
@@ -58,7 +58,7 @@ services:
     # ports:
     #   - "5432:5432"
     volumes:
-      - postgres_data:/var/lib/postgresql/data/pgdata
+      - postgres_data:/var/lib/postgresql/data
 
 volumes:
-  postgres_data:
\ No newline at end of file
+  postgres_data:
diff --git a/docker-compose.yml b/docker-compose.yml
index 24a726164b0748b765bc70763c045545301f5d6f..f0620918cf5b404677d3f96829cc3fbfeaea28d5 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -42,7 +42,7 @@ services:
     # ports:
     #   - "5432:5432"
     volumes:
-      - postgres_data:/var/lib/postgresql/data/pgdata
+      - postgres_data:/var/lib/postgresql/data
 
 volumes:
   postgres_data:
diff --git a/package.json b/package.json
index d46816f0bb78a2660f85cf06d406e7aa09cef067..d4e94f017f5b183d6483c4a698adbc908a53845e 100644
--- a/package.json
+++ b/package.json
@@ -50,6 +50,6 @@
     "type": "git",
     "url": "https://github.com/ether/etherpad-lite.git"
   },
-  "version": "2.2.4",
+  "version": "2.2.5",
   "license": "Apache-2.0"
 }
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 17eff3bd8e95d3417cf6bac7593cb5de5a3574a3..421cb977ab51e102eddf1cb53810585f31925069 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -26,47 +26,47 @@ importers:
     dependencies:
       '@radix-ui/react-switch':
         specifier: ^1.1.0
-        version: 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+        version: 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
     devDependencies:
       '@radix-ui/react-dialog':
         specifier: ^1.1.1
-        version: 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+        version: 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
       '@radix-ui/react-toast':
         specifier: ^1.2.1
-        version: 1.2.1(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+        version: 1.2.1(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
       '@types/react':
-        specifier: ^18.3.5
-        version: 18.3.5
+        specifier: ^18.3.8
+        version: 18.3.8
       '@types/react-dom':
         specifier: ^18.2.25
         version: 18.3.0
       '@typescript-eslint/eslint-plugin':
-        specifier: ^8.4.0
-        version: 8.4.0(@typescript-eslint/parser@8.4.0(eslint@9.9.1)(typescript@5.5.4))(eslint@9.9.1)(typescript@5.5.4)
+        specifier: ^8.6.0
+        version: 8.6.0(@typescript-eslint/parser@8.6.0(eslint@9.10.0)(typescript@5.6.2))(eslint@9.10.0)(typescript@5.6.2)
       '@typescript-eslint/parser':
-        specifier: ^8.4.0
-        version: 8.4.0(eslint@9.9.1)(typescript@5.5.4)
+        specifier: ^8.6.0
+        version: 8.6.0(eslint@9.10.0)(typescript@5.6.2)
       '@vitejs/plugin-react-swc':
         specifier: ^3.5.0
-        version: 3.7.0(vite@5.4.3(@types/node@22.5.4))
+        version: 3.7.0(vite@5.4.7(@types/node@22.5.5))
       eslint:
-        specifier: ^9.9.1
-        version: 9.9.1
+        specifier: ^9.10.0
+        version: 9.10.0
       eslint-plugin-react-hooks:
         specifier: ^4.6.0
-        version: 4.6.2(eslint@9.9.1)
+        version: 4.6.2(eslint@9.10.0)
       eslint-plugin-react-refresh:
-        specifier: ^0.4.11
-        version: 0.4.11(eslint@9.9.1)
+        specifier: ^0.4.12
+        version: 0.4.12(eslint@9.10.0)
       i18next:
-        specifier: ^23.14.0
-        version: 23.14.0
+        specifier: ^23.15.1
+        version: 23.15.1
       i18next-browser-languagedetector:
         specifier: ^8.0.0
         version: 8.0.0
       lucide-react:
-        specifier: ^0.439.0
-        version: 0.439.0(react@18.3.1)
+        specifier: ^0.441.0
+        version: 0.441.0(react@18.3.1)
       react:
         specifier: ^18.2.0
         version: 18.3.1
@@ -77,29 +77,29 @@ importers:
         specifier: ^7.53.0
         version: 7.53.0(react@18.3.1)
       react-i18next:
-        specifier: ^15.0.1
-        version: 15.0.1(i18next@23.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+        specifier: ^15.0.2
+        version: 15.0.2(i18next@23.15.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
       react-router-dom:
-        specifier: ^6.26.1
-        version: 6.26.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+        specifier: ^6.26.2
+        version: 6.26.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
       socket.io-client:
         specifier: ^4.7.5
         version: 4.7.5
       typescript:
-        specifier: ^5.5.4
-        version: 5.5.4
+        specifier: ^5.6.2
+        version: 5.6.2
       vite:
-        specifier: ^5.4.3
-        version: 5.4.3(@types/node@22.5.4)
+        specifier: ^5.4.7
+        version: 5.4.7(@types/node@22.5.5)
       vite-plugin-static-copy:
         specifier: ^1.0.6
-        version: 1.0.6(vite@5.4.3(@types/node@22.5.4))
+        version: 1.0.6(vite@5.4.7(@types/node@22.5.5))
       vite-plugin-svgr:
         specifier: ^4.2.0
-        version: 4.2.0(rollup@4.21.0)(typescript@5.5.4)(vite@5.4.3(@types/node@22.5.4))
+        version: 4.2.0(rollup@4.21.0)(typescript@5.6.2)(vite@5.4.7(@types/node@22.5.5))
       zustand:
         specifier: ^4.5.5
-        version: 4.5.5(@types/react@18.3.5)(react@18.3.1)
+        version: 4.5.5(@types/react@18.3.8)(react@18.3.1)
 
   bin:
     dependencies:
@@ -116,27 +116,27 @@ importers:
         specifier: ^7.6.3
         version: 7.6.3
       tsx:
-        specifier: ^4.19.0
-        version: 4.19.0
+        specifier: ^4.19.1
+        version: 4.19.1
       ueberdb2:
-        specifier: ^4.2.103
-        version: 4.2.103
+        specifier: ^5.0.2
+        version: 5.0.2
     devDependencies:
       '@types/node':
-        specifier: ^22.5.4
-        version: 22.5.4
+        specifier: ^22.5.5
+        version: 22.5.5
       '@types/semver':
         specifier: ^7.5.8
         version: 7.5.8
       typescript:
-        specifier: ^5.5.4
-        version: 5.5.4
+        specifier: ^5.6.2
+        version: 5.6.2
 
   doc:
     devDependencies:
       vitepress:
         specifier: ^1.3.4
-        version: 1.3.4(@algolia/client-search@4.23.3)(@types/node@22.5.4)(@types/react@18.3.5)(axios@1.7.7)(postcss@8.4.45)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.4)
+        version: 1.3.4(@algolia/client-search@4.23.3)(@types/node@22.5.5)(@types/react@18.3.8)(axios@1.7.7)(postcss@8.4.45)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.2)
 
   src:
     dependencies:
@@ -165,11 +165,11 @@ importers:
         specifier: ^0.23.1
         version: 0.23.1
       express:
-        specifier: 4.19.2
-        version: 4.19.2
+        specifier: 4.21.0
+        version: 4.21.0
       express-rate-limit:
         specifier: ^7.4.0
-        version: 7.4.0(express@4.19.2)
+        version: 7.4.0(express@4.21.0)
       fast-deep-equal:
         specifier: ^3.1.3
         version: 3.1.3
@@ -183,8 +183,8 @@ importers:
         specifier: ^2.0.0
         version: 2.0.0
       jose:
-        specifier: ^5.8.0
-        version: 5.8.0
+        specifier: ^5.9.2
+        version: 5.9.2
       js-cookie:
         specifier: ^3.0.5
         version: 3.0.5
@@ -222,8 +222,8 @@ importers:
         specifier: ^8.5.1
         version: 8.5.1
       openapi-backend:
-        specifier: ^5.10.6
-        version: 5.10.6
+        specifier: ^5.11.0
+        version: 5.11.0
       proxy-addr:
         specifier: ^2.0.7
         version: 2.0.7
@@ -234,14 +234,14 @@ importers:
         specifier: ^13.0.1
         version: 13.0.1
       rehype-minify-whitespace:
-        specifier: ^6.0.0
-        version: 6.0.0
+        specifier: ^6.0.1
+        version: 6.0.1
       resolve:
         specifier: 1.22.8
         version: 1.22.8
       rusty-store-kv:
-        specifier: ^1.2.0
-        version: 1.2.0
+        specifier: ^1.3.1
+        version: 1.3.1
       security:
         specifier: 1.0.0
         version: 1.0.0
@@ -257,15 +257,18 @@ importers:
       superagent:
         specifier: 10.1.0
         version: 10.1.0
+      swagger-ui-express:
+        specifier: ^5.0.1
+        version: 5.0.1(express@4.21.0)
       tinycon:
         specifier: 0.6.8
         version: 0.6.8
       tsx:
-        specifier: 4.19.0
-        version: 4.19.0
+        specifier: 4.19.1
+        version: 4.19.1
       ueberdb2:
-        specifier: ^4.2.103
-        version: 4.2.103
+        specifier: ^5.0.2
+        version: 5.0.2
       underscore:
         specifier: 1.13.7
         version: 1.13.7
@@ -277,8 +280,8 @@ importers:
         version: 0.9.3
     devDependencies:
       '@playwright/test':
-        specifier: ^1.47.0
-        version: 1.47.0
+        specifier: ^1.47.1
+        version: 1.47.1
       '@types/async':
         specifier: ^3.2.24
         version: 3.2.24
@@ -301,17 +304,17 @@ importers:
         specifier: ^21.1.7
         version: 21.1.7
       '@types/jsonwebtoken':
-        specifier: ^9.0.6
-        version: 9.0.6
+        specifier: ^9.0.7
+        version: 9.0.7
       '@types/mime-types':
         specifier: ^2.1.4
         version: 2.1.4
       '@types/mocha':
-        specifier: ^10.0.7
-        version: 10.0.7
+        specifier: ^10.0.8
+        version: 10.0.8
       '@types/node':
-        specifier: ^22.5.4
-        version: 22.5.4
+        specifier: ^22.5.5
+        version: 22.5.5
       '@types/oidc-provider':
         specifier: ^8.5.2
         version: 8.5.2
@@ -324,18 +327,24 @@ importers:
       '@types/supertest':
         specifier: ^6.0.2
         version: 6.0.2
+      '@types/swagger-ui-express':
+        specifier: ^4.1.6
+        version: 4.1.6
       '@types/underscore':
         specifier: ^1.11.15
         version: 1.11.15
+      '@types/whatwg-mimetype':
+        specifier: ^3.0.2
+        version: 3.0.2
       chokidar:
-        specifier: ^3.6.0
-        version: 3.6.0
+        specifier: ^4.0.0
+        version: 4.0.0
       eslint:
-        specifier: ^9.9.1
-        version: 9.9.1
+        specifier: ^9.10.0
+        version: 9.10.0
       eslint-config-etherpad:
         specifier: ^4.0.4
-        version: 4.0.4(eslint@9.9.1)(typescript@5.5.4)
+        version: 4.0.4(eslint@9.10.0)(typescript@5.6.2)
       etherpad-cli-client:
         specifier: ^3.0.2
         version: 3.0.2
@@ -355,8 +364,8 @@ importers:
         specifier: ^2.7.0
         version: 2.7.0
       sinon:
-        specifier: ^18.0.0
-        version: 18.0.0
+        specifier: ^19.0.2
+        version: 19.0.2
       split-grid:
         specifier: ^1.0.11
         version: 1.0.11
@@ -364,11 +373,11 @@ importers:
         specifier: ^7.0.0
         version: 7.0.0
       typescript:
-        specifier: ^5.5.4
-        version: 5.5.4
+        specifier: ^5.6.2
+        version: 5.6.2
       vitest:
-        specifier: ^2.0.5
-        version: 2.0.5(@types/node@22.5.4)(jsdom@25.0.0)
+        specifier: ^2.1.1
+        version: 2.1.1(@types/node@22.5.5)(jsdom@25.0.0)
 
   ui:
     devDependencies:
@@ -376,11 +385,11 @@ importers:
         specifier: workspace:../src
         version: link:../src
       typescript:
-        specifier: ^5.5.4
-        version: 5.5.4
+        specifier: ^5.6.2
+        version: 5.6.2
       vite:
-        specifier: ^5.4.3
-        version: 5.4.3(@types/node@22.5.4)
+        specifier: ^5.4.7
+        version: 5.4.7(@types/node@22.5.5)
 
 packages:
 
@@ -540,6 +549,10 @@ packages:
     resolution: {integrity: sha512-5F7SDGs1T72ZczbRwbGO9lQi0NLjQxzl6i4lJxLxfW9U5UluCSyEJeniWvnhl3/euNiqQVbo8zruhsDfid0esA==}
     engines: {node: '>=6.9.0'}
 
+  '@babel/runtime@7.25.6':
+    resolution: {integrity: sha512-VBj9MYyDb9tuLq7yzqjgzt6Q+IBQLrGZfdjOekyEirZPHxXWoTSGUTMrpsfi58Up73d13NfYLv8HT9vmznjzhQ==}
+    engines: {node: '>=6.9.0'}
+
   '@babel/template@7.24.7':
     resolution: {integrity: sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==}
     engines: {node: '>=6.9.0'}
@@ -875,14 +888,18 @@ packages:
     resolution: {integrity: sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
 
-  '@eslint/js@9.9.1':
-    resolution: {integrity: sha512-xIDQRsfg5hNBqHz04H1R3scSVwmI+KUbqjsQKHKQ1DAUSaUjYPReZZmS/5PNiKu1fUvzDd6H7DEDKACSEhu+TQ==}
+  '@eslint/js@9.10.0':
+    resolution: {integrity: sha512-fuXtbiP5GWIn8Fz+LWoOMVf/Jxm+aajZYkhi6CuEm4SxymFM+eUWzbO9qXT+L0iCkL5+KGYMCSGxo686H19S1g==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
 
   '@eslint/object-schema@2.1.4':
     resolution: {integrity: sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
 
+  '@eslint/plugin-kit@0.1.0':
+    resolution: {integrity: sha512-autAXT203ixhqei9xt+qkYOvY8l6LAFIdT2UXc/RPNeUVfqRF1BV94GTJyVPFKT8nFM6MyVJhjLj9E8JWvf5zQ==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
   '@etherpad/express-session@1.18.4':
     resolution: {integrity: sha512-uiUtcfv0hyEA+Lur00V6yINaa/qe09HiFqmc+DzSXYChILFLgOV3G4p4XJkIRrUOGmqaJRiliB1BoQIiY3Tnjw==}
     engines: {node: '>= 0.8.0'}
@@ -907,8 +924,8 @@ packages:
     resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==}
     engines: {node: '>=6.0.0'}
 
-  '@jridgewell/sourcemap-codec@1.4.15':
-    resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==}
+  '@jridgewell/sourcemap-codec@1.5.0':
+    resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==}
 
   '@jridgewell/trace-mapping@0.3.25':
     resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
@@ -923,6 +940,7 @@ packages:
   '@koa/router@12.0.1':
     resolution: {integrity: sha512-ribfPYfHb+Uw3b27Eiw6NPqjhIhTpVFzEWLwyc/1Xp+DCdwRRyIlAUODX+9bPARF6aQtUu1+/PHzdNvRzcs/+Q==}
     engines: {node: '>= 12'}
+    deprecated: Use v12.0.2 or higher to fix the vulnerability issue
 
   '@nodelib/fs.scandir@2.1.5':
     resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
@@ -936,8 +954,8 @@ packages:
     resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
     engines: {node: '>= 8'}
 
-  '@playwright/test@1.47.0':
-    resolution: {integrity: sha512-SgAdlSwYVpToI4e/IH19IHHWvoijAYH5hu2MWSXptRypLSnzj51PcGD+rsOXFayde4P9ZLi+loXVwArg6IUkCA==}
+  '@playwright/test@1.47.1':
+    resolution: {integrity: sha512-dbWpcNQZ5nj16m+A5UNScYx7HX5trIy7g4phrcitn+Nk83S32EBX/CLU4hiF4RGKX/yRc93AAqtfaXB7JWBd4Q==}
     engines: {node: '>=18'}
     hasBin: true
 
@@ -1173,8 +1191,8 @@ packages:
       '@types/react-dom':
         optional: true
 
-  '@remix-run/router@1.19.1':
-    resolution: {integrity: sha512-S45oynt/WH19bHbIXjtli6QmwNYvaz+vtnubvNpNDvUOoA/OWh6j1OikIP3G+v5GHdxyC6EXoChG3HgYGEUfcg==}
+  '@remix-run/router@1.19.2':
+    resolution: {integrity: sha512-baiMx18+IMuD1yyvOGaHM9QrVUPGGG0jC+z+IPHnRJWUAUvaKuWKyE8gjDj2rzv3sz9zOGoRSPgeBVHRhZnBlA==}
     engines: {node: '>=14.0.0'}
 
   '@rollup/pluginutils@5.1.0':
@@ -1279,20 +1297,17 @@ packages:
     resolution: {integrity: sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==}
     engines: {node: '>=14.16'}
 
-  '@sinonjs/commons@2.0.0':
-    resolution: {integrity: sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==}
-
   '@sinonjs/commons@3.0.1':
     resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==}
 
-  '@sinonjs/fake-timers@11.2.2':
-    resolution: {integrity: sha512-G2piCSxQ7oWOxwGSAyFHfPIsyeJGXYtc6mFbnFA+kRXkiEnTl8c/8jul2S329iFBnDI9HGoeWWAZvuvOkZccgw==}
+  '@sinonjs/fake-timers@13.0.2':
+    resolution: {integrity: sha512-4Bb+oqXZTSTZ1q27Izly9lv8B9dlV61CROxPiVtywwzv5SnytJqhvYe6FclHYuXml4cd1VHPo1zd5PmTeJozvA==}
 
-  '@sinonjs/samsam@8.0.0':
-    resolution: {integrity: sha512-Bp8KUVlLp8ibJZrnvq2foVhP0IVX2CIprMJPK0vqGqgrDa0OHVKeZyBykqskkrdxV6yKBPmGasO8LVjAKR3Gew==}
+  '@sinonjs/samsam@8.0.2':
+    resolution: {integrity: sha512-v46t/fwnhejRSFTGqbpn9u+LQ9xJDse10gNnPgAcxgdoCDMXj/G2asWAC/8Qs+BAZDicX+MNZouXT1A7c83kVw==}
 
-  '@sinonjs/text-encoding@0.7.2':
-    resolution: {integrity: sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==}
+  '@sinonjs/text-encoding@0.7.3':
+    resolution: {integrity: sha512-DE427ROAphMQzU4ENbliGYrBSYPXF+TtLg9S8vzeA+OF4ZKzoDdzfL8sxuMUGS/lgRhM6j1URSk9ghf7Xo1tyA==}
 
   '@socket.io/component-emitter@3.1.2':
     resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==}
@@ -1519,8 +1534,8 @@ packages:
   '@types/json5@0.0.29':
     resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
 
-  '@types/jsonwebtoken@9.0.6':
-    resolution: {integrity: sha512-/5hndP5dCjloafCXns6SZyESp3Ldq7YjH3zwzwczYnjxIT0Fqzk5ROSYVGfFyczIue7IUEj8hkvLbPoLQ18vQw==}
+  '@types/jsonwebtoken@9.0.7':
+    resolution: {integrity: sha512-ugo316mmTYBl2g81zDFnZ7cfxlut3o+/EQdaP7J8QN2kY6lJ22hmQYCK5EHcJHbrW+dkCGSCPgbG8JtYj6qSrg==}
 
   '@types/keygrip@1.0.6':
     resolution: {integrity: sha512-lZuNAY9xeJt7Bx4t4dx0rYCDqGPW8RXhQZK1td7d4H6E9zYbLoOtjBvfwdTKpsyxQI/2jv+armjX/RW+ZNpXOQ==}
@@ -1555,8 +1570,8 @@ packages:
   '@types/mime@1.3.5':
     resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==}
 
-  '@types/mocha@10.0.7':
-    resolution: {integrity: sha512-GN8yJ1mNTcFcah/wKEFIJckJx9iJLoMSzWcfRRuxz/Jk+U6KQNnml+etbtxFK8lPjzOw3zp4Ha/kjSst9fsHYw==}
+  '@types/mocha@10.0.8':
+    resolution: {integrity: sha512-HfMcUmy9hTMJh66VNcmeC9iVErIZJli2bszuXc6julh5YGuRb/W5OnkHjwLNYdFlMis0sY3If5SEAp+PktdJjw==}
 
   '@types/ms@0.7.34':
     resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==}
@@ -1564,8 +1579,8 @@ packages:
   '@types/node-fetch@2.6.11':
     resolution: {integrity: sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==}
 
-  '@types/node@22.5.4':
-    resolution: {integrity: sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==}
+  '@types/node@22.5.5':
+    resolution: {integrity: sha512-Xjs4y5UPO/CLdzpgR6GirZJx36yScjh73+2NlLlkFRSoQN8B0DpfXPdZGnvVmLRLOsqDpOfTNv7D9trgGhmOIA==}
 
   '@types/oidc-provider@8.5.2':
     resolution: {integrity: sha512-NiD3VG49+cRCAAe8+uZLM4onOcX8y9+cwaml8JG1qlgc98rWoCRgsnOB4Ypx+ysays5jiwzfUgT0nWyXPB/9uQ==}
@@ -1582,8 +1597,8 @@ packages:
   '@types/react-dom@18.3.0':
     resolution: {integrity: sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==}
 
-  '@types/react@18.3.5':
-    resolution: {integrity: sha512-WeqMfGJLGuLCqHGYRGHxnKrXcTitc6L/nBUWfWPcTarG3t9PsquqUMuVeXZeca+mglY4Vo5GZjCi0A3Or2lnxA==}
+  '@types/react@18.3.8':
+    resolution: {integrity: sha512-syBUrW3/XpnW4WJ41Pft+I+aPoDVbrBVQGEnbD7NijDGlVC+8gV/XKRY+7vMDlfPpbwYt0l1vd/Sj8bJGMbs9Q==}
 
   '@types/semver@7.5.8':
     resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==}
@@ -1609,6 +1624,9 @@ packages:
   '@types/supertest@6.0.2':
     resolution: {integrity: sha512-137ypx2lk/wTQbW6An6safu9hXmajAifU/s7szAHLN/FeIm5w7yR0Wkl9fdJMRSHwOn4HLAI0DaB2TOORuhPDg==}
 
+  '@types/swagger-ui-express@4.1.6':
+    resolution: {integrity: sha512-UVSiGYXa5IzdJJG3hrc86e8KdZWLYxyEsVoUI4iPXc7CO4VZ3AfNP8d/8+hrDRIqz+HAaSMtZSqAsF3Nq2X/Dg==}
+
   '@types/tar@6.1.13':
     resolution: {integrity: sha512-IznnlmU5f4WcGTh2ltRu/Ijpmk8wiWXfF0VA4s+HPjHZgvFggk1YaIkbo5krX/zUCzWF8N/l4+W/LNxnvAJ8nw==}
 
@@ -1627,6 +1645,9 @@ packages:
   '@types/web-bluetooth@0.0.20':
     resolution: {integrity: sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==}
 
+  '@types/whatwg-mimetype@3.0.2':
+    resolution: {integrity: sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA==}
+
   '@typescript-eslint/eslint-plugin@7.17.0':
     resolution: {integrity: sha512-pyiDhEuLM3PuANxH7uNYan1AaFs5XE0zw1hq69JBvGvE7gSuEoQl1ydtEe/XQeoC3GQxLXyOVa5kNOATgM638A==}
     engines: {node: ^18.18.0 || >=20.0.0}
@@ -1638,8 +1659,8 @@ packages:
       typescript:
         optional: true
 
-  '@typescript-eslint/eslint-plugin@8.4.0':
-    resolution: {integrity: sha512-rg8LGdv7ri3oAlenMACk9e+AR4wUV0yrrG+XKsGKOK0EVgeEDqurkXMPILG2836fW4ibokTB5v4b6Z9+GYQDEw==}
+  '@typescript-eslint/eslint-plugin@8.6.0':
+    resolution: {integrity: sha512-UOaz/wFowmoh2G6Mr9gw60B1mm0MzUtm6Ic8G2yM1Le6gyj5Loi/N+O5mocugRGY+8OeeKmkMmbxNqUCq3B4Sg==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
     peerDependencies:
       '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0
@@ -1659,8 +1680,8 @@ packages:
       typescript:
         optional: true
 
-  '@typescript-eslint/parser@8.4.0':
-    resolution: {integrity: sha512-NHgWmKSgJk5K9N16GIhQ4jSobBoJwrmURaLErad0qlLjrpP5bECYg+wxVTGlGZmJbU03jj/dfnb6V9bw+5icsA==}
+  '@typescript-eslint/parser@8.6.0':
+    resolution: {integrity: sha512-eQcbCuA2Vmw45iGfcyG4y6rS7BhWfz9MQuk409WD47qMM+bKCGQWXxvoOs1DUp+T7UBMTtRTVT+kXr7Sh4O9Ow==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
     peerDependencies:
       eslint: ^8.57.0 || ^9.0.0
@@ -1673,8 +1694,8 @@ packages:
     resolution: {integrity: sha512-0P2jTTqyxWp9HiKLu/Vemr2Rg1Xb5B7uHItdVZ6iAenXmPo4SZ86yOPCJwMqpCyaMiEHTNqizHfsbmCFT1x9SA==}
     engines: {node: ^18.18.0 || >=20.0.0}
 
-  '@typescript-eslint/scope-manager@8.4.0':
-    resolution: {integrity: sha512-n2jFxLeY0JmKfUqy3P70rs6vdoPjHK8P/w+zJcV3fk0b0BwRXC/zxRTEnAsgYT7MwdQDt/ZEbtdzdVC+hcpF0A==}
+  '@typescript-eslint/scope-manager@8.6.0':
+    resolution: {integrity: sha512-ZuoutoS5y9UOxKvpc/GkvF4cuEmpokda4wRg64JEia27wX+PysIE9q+lzDtlHHgblwUWwo5/Qn+/WyTUvDwBHw==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
 
   '@typescript-eslint/type-utils@7.17.0':
@@ -1687,8 +1708,8 @@ packages:
       typescript:
         optional: true
 
-  '@typescript-eslint/type-utils@8.4.0':
-    resolution: {integrity: sha512-pu2PAmNrl9KX6TtirVOrbLPLwDmASpZhK/XU7WvoKoCUkdtq9zF7qQ7gna0GBZFN0hci0vHaSusiL2WpsQk37A==}
+  '@typescript-eslint/type-utils@8.6.0':
+    resolution: {integrity: sha512-dtePl4gsuenXVwC7dVNlb4mGDcKjDT/Ropsk4za/ouMBPplCLyznIaR+W65mvCvsyS97dymoBRrioEXI7k0XIg==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
     peerDependencies:
       typescript: '*'
@@ -1700,8 +1721,8 @@ packages:
     resolution: {integrity: sha512-a29Ir0EbyKTKHnZWbNsrc/gqfIBqYPwj3F2M+jWE/9bqfEHg0AMtXzkbUkOG6QgEScxh2+Pz9OXe11jHDnHR7A==}
     engines: {node: ^18.18.0 || >=20.0.0}
 
-  '@typescript-eslint/types@8.4.0':
-    resolution: {integrity: sha512-T1RB3KQdskh9t3v/qv7niK6P8yvn7ja1mS7QK7XfRVL6wtZ8/mFs/FHf4fKvTA0rKnqnYxl/uHFNbnEt0phgbw==}
+  '@typescript-eslint/types@8.6.0':
+    resolution: {integrity: sha512-rojqFZGd4MQxw33SrOy09qIDS8WEldM8JWtKQLAjf/X5mGSeEFh5ixQlxssMNyPslVIk9yzWqXCsV2eFhYrYUw==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
 
   '@typescript-eslint/typescript-estree@7.17.0':
@@ -1713,8 +1734,8 @@ packages:
       typescript:
         optional: true
 
-  '@typescript-eslint/typescript-estree@8.4.0':
-    resolution: {integrity: sha512-kJ2OIP4dQw5gdI4uXsaxUZHRwWAGpREJ9Zq6D5L0BweyOrWsL6Sz0YcAZGWhvKnH7fm1J5YFE1JrQL0c9dd53A==}
+  '@typescript-eslint/typescript-estree@8.6.0':
+    resolution: {integrity: sha512-MOVAzsKJIPIlLK239l5s06YXjNqpKTVhBVDnqUumQJja5+Y94V3+4VUFRA0G60y2jNnTVwRCkhyGQpavfsbq/g==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
     peerDependencies:
       typescript: '*'
@@ -1728,8 +1749,8 @@ packages:
     peerDependencies:
       eslint: ^8.56.0
 
-  '@typescript-eslint/utils@8.4.0':
-    resolution: {integrity: sha512-swULW8n1IKLjRAgciCkTCafyTHHfwVQFt8DovmaF69sKbOxTSFMmIZaSHjqO9i/RV0wIblaawhzvtva8Nmm7lQ==}
+  '@typescript-eslint/utils@8.6.0':
+    resolution: {integrity: sha512-eNp9cWnYf36NaOVjkEUznf6fEgVy1TWpE0o52e4wtojjBx7D1UV2WAWGzR+8Y5lVFtpMLPwNbC67T83DWSph4A==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
     peerDependencies:
       eslint: ^8.57.0 || ^9.0.0
@@ -1738,8 +1759,8 @@ packages:
     resolution: {integrity: sha512-RVGC9UhPOCsfCdI9pU++K4nD7to+jTcMIbXTSOcrLqUEW6gF2pU1UUbYJKc9cvcRSK1UDeMJ7pdMxf4bhMpV/A==}
     engines: {node: ^18.18.0 || >=20.0.0}
 
-  '@typescript-eslint/visitor-keys@8.4.0':
-    resolution: {integrity: sha512-zTQD6WLNTre1hj5wp09nBIDiOc2U5r/qmzo7wxPn4ZgAjHql09EofqhF9WF+fZHzL5aCyaIpPcT2hyxl73kr9A==}
+  '@typescript-eslint/visitor-keys@8.6.0':
+    resolution: {integrity: sha512-wapVFfZg9H0qOYh4grNVQiMklJGluQrOUiOhYRrQWhx7BY/+I1IYb8BczWNbbUpO+pqy0rDciv3lQH5E1bCLrg==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
 
   '@ungap/structured-clone@1.2.0':
@@ -1757,23 +1778,34 @@ packages:
       vite: ^5.0.0
       vue: ^3.2.25
 
-  '@vitest/expect@2.0.5':
-    resolution: {integrity: sha512-yHZtwuP7JZivj65Gxoi8upUN2OzHTi3zVfjwdpu2WrvCZPLwsJ2Ey5ILIPccoW23dd/zQBlJ4/dhi7DWNyXCpA==}
+  '@vitest/expect@2.1.1':
+    resolution: {integrity: sha512-YeueunS0HiHiQxk+KEOnq/QMzlUuOzbU1Go+PgAsHvvv3tUkJPm9xWt+6ITNTlzsMXUjmgm5T+U7KBPK2qQV6w==}
 
-  '@vitest/pretty-format@2.0.5':
-    resolution: {integrity: sha512-h8k+1oWHfwTkyTkb9egzwNMfJAEx4veaPSnMeKbVSjp4euqGSbQlm5+6VHwTr7u4FJslVVsUG5nopCaAYdOmSQ==}
+  '@vitest/mocker@2.1.1':
+    resolution: {integrity: sha512-LNN5VwOEdJqCmJ/2XJBywB11DLlkbY0ooDJW3uRX5cZyYCrc4PI/ePX0iQhE3BiEGiQmK4GE7Q/PqCkkaiPnrA==}
+    peerDependencies:
+      msw: ^2.3.5
+      vite: ^5.0.0
+    peerDependenciesMeta:
+      msw:
+        optional: true
+      vite:
+        optional: true
+
+  '@vitest/pretty-format@2.1.1':
+    resolution: {integrity: sha512-SjxPFOtuINDUW8/UkElJYQSFtnWX7tMksSGW0vfjxMneFqxVr8YJ979QpMbDW7g+BIiq88RAGDjf7en6rvLPPQ==}
 
-  '@vitest/runner@2.0.5':
-    resolution: {integrity: sha512-TfRfZa6Bkk9ky4tW0z20WKXFEwwvWhRY+84CnSEtq4+3ZvDlJyY32oNTJtM7AW9ihW90tX/1Q78cb6FjoAs+ig==}
+  '@vitest/runner@2.1.1':
+    resolution: {integrity: sha512-uTPuY6PWOYitIkLPidaY5L3t0JJITdGTSwBtwMjKzo5O6RCOEncz9PUN+0pDidX8kTHYjO0EwUIvhlGpnGpxmA==}
 
-  '@vitest/snapshot@2.0.5':
-    resolution: {integrity: sha512-SgCPUeDFLaM0mIUHfaArq8fD2WbaXG/zVXjRupthYfYGzc8ztbFbu6dUNOblBG7XLMR1kEhS/DNnfCZ2IhdDew==}
+  '@vitest/snapshot@2.1.1':
+    resolution: {integrity: sha512-BnSku1WFy7r4mm96ha2FzN99AZJgpZOWrAhtQfoxjUU5YMRpq1zmHRq7a5K9/NjqonebO7iVDla+VvZS8BOWMw==}
 
-  '@vitest/spy@2.0.5':
-    resolution: {integrity: sha512-c/jdthAhvJdpfVuaexSrnawxZz6pywlTPe84LUB2m/4t3rl2fTo9NFGBG4oWgaD+FTgDDV8hJ/nibT7IfH3JfA==}
+  '@vitest/spy@2.1.1':
+    resolution: {integrity: sha512-ZM39BnZ9t/xZ/nF4UwRH5il0Sw93QnZXd9NAZGRpIgj0yvVwPpLd702s/Cx955rGaMlyBQkZJ2Ir7qyY48VZ+g==}
 
-  '@vitest/utils@2.0.5':
-    resolution: {integrity: sha512-d8HKbqIcya+GR67mkZbrzhS5kKhtp8dQLcmRZLGTscGVg7yImT82cIrhtn2L8+VujWcy6KZweApgNmPsTAO/UQ==}
+  '@vitest/utils@2.1.1':
+    resolution: {integrity: sha512-Y6Q9TsI+qJ2CC0ZKj6VBb+T8UPz593N113nnUykqwANqhgf3QkZeHFlusgKLTqrnVHbj/XDKZcDHol+dxVT+rQ==}
 
   '@vue/compiler-core@3.4.38':
     resolution: {integrity: sha512-8IQOTCWnLFqfHzOGm9+P8OPSEDukgg3Huc92qSG49if/xI2SAwLHQO2qaPQbjCWPBcQoO1WYfXfTACUrWV3c5A==}
@@ -2007,8 +2039,8 @@ packages:
   birpc@0.2.17:
     resolution: {integrity: sha512-+hkTxhot+dWsLpp3gia5AkVHIsKlZybNT5gIYiDlNzJrmYPcTM9k5/w2uaj3IPpd7LlEYpmCj4Jj1nC41VhDFg==}
 
-  body-parser@1.20.2:
-    resolution: {integrity: sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==}
+  body-parser@1.20.3:
+    resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==}
     engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
 
   brace-expansion@1.1.11:
@@ -2103,6 +2135,10 @@ packages:
     resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
     engines: {node: '>= 8.10.0'}
 
+  chokidar@4.0.0:
+    resolution: {integrity: sha512-mxIojEAQcuEvT/lyXq+jf/3cO/KoA6z4CeNDGGevTybECPOMFCnQy3OPahluUkbqgPNGw5Bi78UC7Po6Lhy+NA==}
+    engines: {node: '>= 14.16.0'}
+
   chownr@2.0.0:
     resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==}
     engines: {node: '>=10'}
@@ -2263,6 +2299,15 @@ packages:
       supports-color:
         optional: true
 
+  debug@4.3.7:
+    resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==}
+    engines: {node: '>=6.0'}
+    peerDependencies:
+      supports-color: '*'
+    peerDependenciesMeta:
+      supports-color:
+        optional: true
+
   decamelize@4.0.0:
     resolution: {integrity: sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==}
     engines: {node: '>=10'}
@@ -2339,6 +2384,10 @@ packages:
     resolution: {integrity: sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==}
     engines: {node: '>=0.3.1'}
 
+  diff@7.0.0:
+    resolution: {integrity: sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==}
+    engines: {node: '>=0.3.1'}
+
   dir-glob@3.0.1:
     resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==}
     engines: {node: '>=8'}
@@ -2371,6 +2420,10 @@ packages:
     resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==}
     engines: {node: '>= 0.8'}
 
+  encodeurl@2.0.0:
+    resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==}
+    engines: {node: '>= 0.8'}
+
   engine.io-client@6.5.4:
     resolution: {integrity: sha512-GeZeeRjpD2qf49cZQ0Wvh/8NJNfeXkXXcoGh+F77oEAgo9gUHwT1fCRxSNU+YEEaysOJTnsFHmM5oAcPy4ntvQ==}
 
@@ -2547,8 +2600,8 @@ packages:
     peerDependencies:
       eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0
 
-  eslint-plugin-react-refresh@0.4.11:
-    resolution: {integrity: sha512-wrAKxMbVr8qhXTtIKfXqAn5SAtRZt0aXxe5P23Fh4pUAdC6XEsybGLB8P0PI4j1yYqOgUEUlzKAGDfo7rJOjcw==}
+  eslint-plugin-react-refresh@0.4.12:
+    resolution: {integrity: sha512-9neVjoGv20FwYtCP6CB1dzR1vr57ZDNOXst21wd2xJ/cTlM2xLq0GWVlSNTdMn/4BtP6cHYBMCSp1wFBJ9jBsg==}
     peerDependencies:
       eslint: '>=7'
 
@@ -2578,8 +2631,8 @@ packages:
     resolution: {integrity: sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
 
-  eslint@9.9.1:
-    resolution: {integrity: sha512-dHvhrbfr4xFQ9/dq+jcVneZMyRYLjggWjk6RVsIiHsP8Rz6yZ8LvZ//iU4TrZF+SXWG+JkNF2OyiZRvzgRDqMg==}
+  eslint@9.10.0:
+    resolution: {integrity: sha512-Y4D0IgtBZfOcOUAIQTSXBKoNGfY0REGqHJG6+Q81vNippW5YlKjHFj4soMxamKK1NXHUWuBZTLdU3Km+L/pcHw==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
     hasBin: true
     peerDependencies:
@@ -2632,18 +2685,14 @@ packages:
     engines: {node: '>=18.0.0'}
     hasBin: true
 
-  execa@8.0.1:
-    resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==}
-    engines: {node: '>=16.17'}
-
   express-rate-limit@7.4.0:
     resolution: {integrity: sha512-v1204w3cXu5gCDmAvgvzI6qjzZzoMWKnyVDk3ACgfswTQLYiGen+r8w0VnXnGMmzEN/g8fwIQ4JrFFd4ZP6ssg==}
     engines: {node: '>= 16'}
     peerDependencies:
       express: 4 || 5 || ^5.0.0-beta.1
 
-  express@4.19.2:
-    resolution: {integrity: sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==}
+  express@4.21.0:
+    resolution: {integrity: sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==}
     engines: {node: '>= 0.10.0'}
 
   extend@3.0.2:
@@ -2690,8 +2739,8 @@ packages:
     resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
     engines: {node: '>=8'}
 
-  finalhandler@1.2.0:
-    resolution: {integrity: sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==}
+  finalhandler@1.3.1:
+    resolution: {integrity: sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==}
     engines: {node: '>= 0.8'}
 
   find-root@1.1.0:
@@ -2815,10 +2864,6 @@ packages:
     resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==}
     engines: {node: '>=10'}
 
-  get-stream@8.0.1:
-    resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==}
-    engines: {node: '>=16'}
-
   get-symbol-description@1.0.2:
     resolution: {integrity: sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==}
     engines: {node: '>= 0.4'}
@@ -2918,6 +2963,9 @@ packages:
   hast-util-is-element@3.0.0:
     resolution: {integrity: sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==}
 
+  hast-util-minify-whitespace@1.0.0:
+    resolution: {integrity: sha512-gD1m4YJSIk62ij32TlhFNqsC3dOQvpA4QAhyZOZFAT4u8LfEfB6N+F0V9oXQGBWXoqrs0h9wQRKa8RCeo8j61g==}
+
   hast-util-parse-selector@4.0.0:
     resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==}
 
@@ -2984,15 +3032,11 @@ packages:
     resolution: {integrity: sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==}
     engines: {node: '>= 14'}
 
-  human-signals@5.0.0:
-    resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==}
-    engines: {node: '>=16.17.0'}
-
   i18next-browser-languagedetector@8.0.0:
     resolution: {integrity: sha512-zhXdJXTTCoG39QsrOCiOabnWj2jecouOqbchu3EfhtSHxIB5Uugnm9JaizenOy39h7ne3+fLikIjeW88+rgszw==}
 
-  i18next@23.14.0:
-    resolution: {integrity: sha512-Y5GL4OdA8IU2geRrt2+Uc1iIhsjICdHZzT9tNwQ3TVqdNzgxHToGCKf/TPRP80vTCAP6svg2WbbJL+Gx5MFQVA==}
+  i18next@23.15.1:
+    resolution: {integrity: sha512-wB4abZ3uK7EWodYisHl/asf8UYEhrI/vj/8aoSsrj/ZDxj4/UXPOa1KvFt1Fq5hkUHquNqwFlDprmjZ8iySgYA==}
 
   iconv-lite@0.4.24:
     resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
@@ -3128,10 +3172,6 @@ packages:
     resolution: {integrity: sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==}
     engines: {node: '>= 0.4'}
 
-  is-stream@3.0.0:
-    resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==}
-    engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
-
   is-string@1.0.7:
     resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==}
     engines: {node: '>= 0.4'}
@@ -3166,8 +3206,8 @@ packages:
     engines: {node: '>=10'}
     hasBin: true
 
-  jose@5.8.0:
-    resolution: {integrity: sha512-E7CqYpL/t7MMnfGnK/eg416OsFCVUrU/Y3Vwe7QjKhu/BkS1Ms455+2xsqZQVN57/U2MHMBvEb5SrmAZWAIntA==}
+  jose@5.9.2:
+    resolution: {integrity: sha512-ILI2xx/I57b20sd7rHZvgiiQrmp2mcotwsAH+5ajbpFQbrYVQdNHYlQhoA5cFb78CgtBOxtC05TeA+mcgkuCqQ==}
 
   js-cookie@3.0.5:
     resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==}
@@ -3362,13 +3402,13 @@ packages:
     resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==}
     engines: {node: '>=12'}
 
-  lucide-react@0.439.0:
-    resolution: {integrity: sha512-PafSWvDTpxdtNEndS2HIHxcNAbd54OaqSYJO90/b63rab2HWYqDbH194j0i82ZFdWOAcf0AHinRykXRRK2PJbw==}
+  lucide-react@0.441.0:
+    resolution: {integrity: sha512-0vfExYtvSDhkC2lqg0zYVW1Uu9GsI4knuV9GP9by5z0Xhc4Zi5RejTxfz9LsjRmCyWVzHCJvxGKZWcRyvQCWVg==}
     peerDependencies:
       react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc
 
-  magic-string@0.30.10:
-    resolution: {integrity: sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==}
+  magic-string@0.30.11:
+    resolution: {integrity: sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==}
 
   mark.js@8.11.1:
     resolution: {integrity: sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==}
@@ -3384,11 +3424,8 @@ packages:
     resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==}
     engines: {node: '>= 0.6'}
 
-  merge-descriptors@1.0.1:
-    resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==}
-
-  merge-stream@2.0.0:
-    resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
+  merge-descriptors@1.0.3:
+    resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==}
 
   merge2@1.4.1:
     resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
@@ -3435,10 +3472,6 @@ packages:
     engines: {node: '>=4.0.0'}
     hasBin: true
 
-  mimic-fn@4.0.0:
-    resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==}
-    engines: {node: '>=12'}
-
   mimic-response@3.1.0:
     resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==}
     engines: {node: '>=10'}
@@ -3529,8 +3562,8 @@ packages:
     resolution: {integrity: sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==}
     engines: {node: '>= 0.4.0'}
 
-  nise@6.0.0:
-    resolution: {integrity: sha512-K8ePqo9BFvN31HXwEtTNGzgrPpmvgciDsFz8aztFjt4LqKO/JeFD8tBOeuDiCMXrIl/m1YvfH8auSpxfaD09wg==}
+  nise@6.1.1:
+    resolution: {integrity: sha512-aMSAzLVY7LyeM60gvBS423nBmIPP+Wy7St7hsb+8/fc1HmeoHJfLO8CKse4u3BtOZvQLJghYPI2i/1WZrEj5/g==}
 
   no-case@3.0.4:
     resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==}
@@ -3557,10 +3590,6 @@ packages:
     resolution: {integrity: sha512-IO9QvjUMWxPQQhs60oOu10CRkWCiZzSUkzbXGGV9pviYl1fXYcvkzQ5jV9z8Y6un8ARoVRl4EtC6v6jNqbaJ/w==}
     engines: {node: '>=14.16'}
 
-  npm-run-path@5.3.0:
-    resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==}
-    engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
-
   nwsapi@2.2.12:
     resolution: {integrity: sha512-qXDmcVlZV4XRtKFzddidpfVP4oMSGhga+xdMc25mv8kaLUHtgzCDhUxkrN8exkGdTlLNaXj7CV3GtON7zuGZ+w==}
 
@@ -3614,15 +3643,11 @@ packages:
   once@1.4.0:
     resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
 
-  onetime@6.0.0:
-    resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==}
-    engines: {node: '>=12'}
-
   only@0.0.2:
     resolution: {integrity: sha512-Fvw+Jemq5fjjyWz6CpKx6w9s7xxqo3+JCyM0WXWeCSOboZ8ABkyvP8ID4CZuChA/wxSx+XSJmdOm8rGVyJ1hdQ==}
 
-  openapi-backend@5.10.6:
-    resolution: {integrity: sha512-vTjBRys/O4JIHdlRHUKZ7pxS+gwIJreAAU9dvYRFrImtPzQ5qxm5a6B8BTVT9m6I8RGGsShJv35MAc3Tu2/y/A==}
+  openapi-backend@5.11.0:
+    resolution: {integrity: sha512-c2p93u0NHUc4Fk2kw4rlReakxNnBw4wMMybOTh0LC/BU0Qp7YIphWwJOfNfq2f9nGe/FeCRxGG6VmtCDgkIjdA==}
     engines: {node: '>=12.0.0'}
 
   openapi-schema-validation@0.4.2:
@@ -3684,19 +3709,19 @@ packages:
     resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
     engines: {node: '>=8'}
 
-  path-key@4.0.0:
-    resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==}
-    engines: {node: '>=12'}
-
   path-parse@1.0.7:
     resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
 
-  path-to-regexp@0.1.7:
-    resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==}
+  path-to-regexp@0.1.10:
+    resolution: {integrity: sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==}
 
   path-to-regexp@6.2.2:
     resolution: {integrity: sha512-GQX3SSMokngb36+whdpRXE+3f9V8UzyAorlYvOGx87ufGHehNTn5lCxrKtLyZ4Yl/wEKnNnr98ZzOwwDZV5ogw==}
 
+  path-to-regexp@8.1.0:
+    resolution: {integrity: sha512-Bqn3vc8CMHty6zuD+tG23s6v2kwxslHEhTj4eYaVKGIEB+YX/2wd0/rgXLFD9G9id9KCtbVy/3ZgmvZjpa0UdQ==}
+    engines: {node: '>=16'}
+
   path-type@4.0.0:
     resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
     engines: {node: '>=8'}
@@ -3718,13 +3743,13 @@ packages:
     resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
     engines: {node: '>=8.6'}
 
-  playwright-core@1.47.0:
-    resolution: {integrity: sha512-1DyHT8OqkcfCkYUD9zzUTfg7EfTd+6a8MkD/NWOvjo0u/SCNd5YmY/lJwFvUZOxJbWNds+ei7ic2+R/cRz/PDg==}
+  playwright-core@1.47.1:
+    resolution: {integrity: sha512-i1iyJdLftqtt51mEk6AhYFaAJCDx0xQ/O5NU8EKaWFgMjItPVma542Nh/Aq8aLCjIJSzjaiEQGW/nyqLkGF1OQ==}
     engines: {node: '>=18'}
     hasBin: true
 
-  playwright@1.47.0:
-    resolution: {integrity: sha512-jOWiRq2pdNAX/mwLiwFYnPHpEZ4rM+fRSQpRHwEwZlP2PUANvL3+aJOF/bvISMhFD30rqMxUB4RJx9aQbfh4Ww==}
+  playwright@1.47.1:
+    resolution: {integrity: sha512-SUEKi6947IqYbKxRiqnbUobVZY4bF1uu+ZnZNJX9DfU1tlf2UhWfvVjLf01pQx9URsOr18bFVUKXmanYWhbfkw==}
     engines: {node: '>=18'}
     hasBin: true
 
@@ -3767,14 +3792,14 @@ packages:
     resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
     engines: {node: '>=6'}
 
-  qs@6.11.0:
-    resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==}
-    engines: {node: '>=0.6'}
-
   qs@6.12.3:
     resolution: {integrity: sha512-AWJm14H1vVaO/iNZ4/hO+HyaTehuy9nRqVdkTqlJt0HWvBiBIEXFmb4C0DGeYo3Xes9rrEW+TxHsaigCbN5ICQ==}
     engines: {node: '>=0.6'}
 
+  qs@6.13.0:
+    resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==}
+    engines: {node: '>=0.6'}
+
   querystringify@2.2.0:
     resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==}
 
@@ -3821,8 +3846,8 @@ packages:
     peerDependencies:
       react: ^16.8.0 || ^17 || ^18 || ^19
 
-  react-i18next@15.0.1:
-    resolution: {integrity: sha512-NwxLqNM6CLbeGA9xPsjits0EnXdKgCRSS6cgkgOdNcPXqL+1fYNl8fBg1wmnnHvFy812Bt4IWTPE9zjoPmFj3w==}
+  react-i18next@15.0.2:
+    resolution: {integrity: sha512-z0W3/RES9Idv3MmJUcf0mDNeeMOUXe+xoL0kPfQPbDoZHmni/XsIoq5zgT2MCFUiau283GuBUK578uD/mkAbLQ==}
     peerDependencies:
       i18next: '>= 23.2.3'
       react: '>= 16.8.0'
@@ -3854,15 +3879,15 @@ packages:
       '@types/react':
         optional: true
 
-  react-router-dom@6.26.1:
-    resolution: {integrity: sha512-veut7m41S1fLql4pLhxeSW3jlqs+4MtjRLj0xvuCEXsxusJCbs6I8yn9BxzzDX2XDgafrccY6hwjmd/bL54tFw==}
+  react-router-dom@6.26.2:
+    resolution: {integrity: sha512-z7YkaEW0Dy35T3/QKPYB1LjMK2R1fxnHO8kWpUMTBdfVzZrWOiY9a7CtN8HqdWtDUWd5FY6Dl8HFsqVwH4uOtQ==}
     engines: {node: '>=14.0.0'}
     peerDependencies:
       react: '>=16.8'
       react-dom: '>=16.8'
 
-  react-router@6.26.1:
-    resolution: {integrity: sha512-kIwJveZNwp7teQRI5QmwWo39A5bXRyqpH0COKKmPnyD2vBvDwgFXSqDUYtt1h+FEyfnE8eXr7oe0MxRzVwCcvQ==}
+  react-router@6.26.2:
+    resolution: {integrity: sha512-tvN1iuT03kHgOFnLPfLJ8V95eijteveqdOSk+srqfePtQvqCExB8eHOYnlilbOcyJyKnYkr1vJvf7YqotAJu1A==}
     engines: {node: '>=14.0.0'}
     peerDependencies:
       react: '>=16.8'
@@ -3885,6 +3910,10 @@ packages:
     resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
     engines: {node: '>=8.10.0'}
 
+  readdirp@4.0.1:
+    resolution: {integrity: sha512-GkMg9uOTpIWWKbSsgwb5fA4EavTR+SG/PMPoAY8hkhHfEEY0/vqljY+XHqtDf2cr2IJtoNRDbrrEpZUiZCkYRw==}
+    engines: {node: '>= 14.16.0'}
+
   regenerator-runtime@0.14.1:
     resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==}
 
@@ -3892,8 +3921,8 @@ packages:
     resolution: {integrity: sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==}
     engines: {node: '>= 0.4'}
 
-  rehype-minify-whitespace@6.0.0:
-    resolution: {integrity: sha512-i9It4YHR0Sf3GsnlR5jFUKXRr9oayvEk9GKQUkwZv6hs70OH9q3OCZrq9PpLvIGKt3W+JxBOxCidNVpH/6rWdA==}
+  rehype-minify-whitespace@6.0.1:
+    resolution: {integrity: sha512-3oJZ3O8ukn6cNJ8elg8dU/tMCH4CDk7elE9x5G+dKL1qQYXeVnsDkSz17sAiUKIoDOXUUkOyC/VMNGEHbPmCew==}
 
   rehype-parse@9.0.0:
     resolution: {integrity: sha512-WG7nfvmWWkCR++KEkZevZb/uw41E8TsH4DsY9UxsTbIXCVGbAs4S+r8FrQ+OtH5EEQAs+5UxKC42VinkmpA1Yw==}
@@ -3954,68 +3983,68 @@ packages:
   run-parallel@1.2.0:
     resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
 
-  rusty-store-kv-darwin-arm64@1.2.0:
-    resolution: {integrity: sha512-3kj/Sgs1WH+6edE8l7Db5+gpROpelYFX8PNloX0y6KzvUQgO1ZDs0Wa8kYG8MEDWIJlmw+7UcwpMmskMleU3Vw==}
+  rusty-store-kv-darwin-arm64@1.3.1:
+    resolution: {integrity: sha512-xJ4kZh22AcNkbl5yIxUFPEZ5xtgOfAn9fH1rcLf0pUHJKTh3FsdrggBqNBlBlJRQ9RWw92MYBSC318mn8mH14Q==}
     engines: {node: '>= 10'}
     cpu: [arm64]
     os: [darwin]
 
-  rusty-store-kv-darwin-x64@1.2.0:
-    resolution: {integrity: sha512-svSiGraZRukY6m330epEnJdu2D54JPzQfMjPRg8LT6f70MELGNxWRirnM/ACIBpWUfh1Ojm9jyK8OoY5IeI4UA==}
+  rusty-store-kv-darwin-x64@1.3.1:
+    resolution: {integrity: sha512-glm0uEKaetb6QBtfe5G7tsXA+tnkBCMDaxb3XCe9oCx3Pr7gR/we6OS9lPJHplaQPp10hGxOn7kjAqLVDlcT3w==}
     engines: {node: '>= 10'}
     cpu: [x64]
     os: [darwin]
 
-  rusty-store-kv-freebsd-x64@1.2.0:
-    resolution: {integrity: sha512-h2/r4PKrRY92z2Ku5I5ACc8yaKdCma0rqak1l5ue/LsR6Zl2oXmzYKXJnzwrqCSIWDRrs5RG2TXMKuPO4telfw==}
+  rusty-store-kv-freebsd-x64@1.3.1:
+    resolution: {integrity: sha512-LWUD+JFvrUlo34XfEsTf29EPsktxqqbGlUHAN/6q5DUjg/s5sBFB2W+C1xlwkD+BumyIkN60ZCRVIoXS2UgJjg==}
     engines: {node: '>= 10'}
     cpu: [x64]
     os: [freebsd]
 
-  rusty-store-kv-linux-arm-gnueabihf@1.2.0:
-    resolution: {integrity: sha512-ivrCOriUYOc/nedCszyPHFATtnLaSaMfNM8h6Cjv4S0gzScpd9rOKVg2hrPh+zApzV/O3dvf6haDtLL1WViKUg==}
+  rusty-store-kv-linux-arm-gnueabihf@1.3.1:
+    resolution: {integrity: sha512-Stpor+kqRZg9ykYLjAJn0YrXfgH96WmTcS1AKeHs6gBhVMJ2RZmn1CyF06g5wVQ7sQGrZWMm8Hp3PGjhM5Z2Ew==}
     engines: {node: '>= 10'}
     cpu: [arm]
     os: [linux]
 
-  rusty-store-kv-linux-arm64-gnu@1.2.0:
-    resolution: {integrity: sha512-dZ3ZWsltagzP3U1J6WqWBRoIakqBP7i7y9MDBnKIFFYALVkvY2q2AhrN5cTBxZHaZTbrjyZyhE/rNbyqLZjXLg==}
+  rusty-store-kv-linux-arm64-gnu@1.3.1:
+    resolution: {integrity: sha512-ZorAn0AumVbL/6kqswhB/vnLEwlSBMvnhDF6LApHhFTRIygq63dyRrwtM8hgEqL/cjOMotGft6INn429V0RWcw==}
     engines: {node: '>= 10'}
     cpu: [arm64]
     os: [linux]
 
-  rusty-store-kv-linux-arm64-musl@1.2.0:
-    resolution: {integrity: sha512-J0Zg+wYzy+03DTFZgMPiTaRTk8dfFJv7zQGpeGgeyma3k7SUmUYT2P1b+0NNjbVxr5xNdFJURyJVglUMP3SJRA==}
+  rusty-store-kv-linux-arm64-musl@1.3.1:
+    resolution: {integrity: sha512-QMNbq7G1Zr2Yk82XqGbs7z2X2gs9mO5lxnHXeHLSy++56EUBTW/zj4JSjdYdetnFBkGwlPSQLAs1s0MXefxc0g==}
     engines: {node: '>= 10'}
     cpu: [arm64]
     os: [linux]
 
-  rusty-store-kv-linux-x64-gnu@1.2.0:
-    resolution: {integrity: sha512-8EK6nzC4o13v7zOtLucoQcSOPSsJe9Rw1cAWk7F5qYg/lfFAQNzOpp68rkCBnrHix8yswcafzcSktJHTKuO4yA==}
+  rusty-store-kv-linux-x64-gnu@1.3.1:
+    resolution: {integrity: sha512-aD6Oj3PlRzLLcIMytTdzkh/mIu0pJjsug2tA8Gfd5lH2SdB6NFVrF/cjrFWgx5LSLcmI+vVpstqjLOIuc3tZ7g==}
     engines: {node: '>= 10'}
     cpu: [x64]
     os: [linux]
 
-  rusty-store-kv-linux-x64-musl@1.2.0:
-    resolution: {integrity: sha512-8Z6xAYWBFKfspSJ1XrRs8P5qYE/aDl6LGhX/5IXg/9mr54quH+pH6YXx88SD0lTgRh+mAD3brCNfr15fOKZftA==}
+  rusty-store-kv-linux-x64-musl@1.3.1:
+    resolution: {integrity: sha512-oSkE6X96muX0cbhE754s7shfzEzUTDQi5d3xrNlA/VskWRjDwKmrqiLHLsxO9lamNcDi5wvK8O6byI9qBXigRg==}
     engines: {node: '>= 10'}
     cpu: [x64]
     os: [linux]
 
-  rusty-store-kv-win32-arm64-msvc@1.2.0:
-    resolution: {integrity: sha512-YmhUBOzJc6yj1Im/WXcD20iuaZ2GWANG/heTk09enzwgza8i68sSlA39TPTyLE0tOZFdbT76GEuIXPHB0peRRg==}
+  rusty-store-kv-win32-arm64-msvc@1.3.1:
+    resolution: {integrity: sha512-HIJ2uJt5LzI/Flx73gnZX/tUfOH2EKS1UKMEzzMF8kqor3iSeGyr0NkLxdl0sZ31dZzRkW63bKxTESmIYjTgiQ==}
     engines: {node: '>= 10'}
     cpu: [arm64]
     os: [win32]
 
-  rusty-store-kv-win32-x64-msvc@1.2.0:
-    resolution: {integrity: sha512-z0/Snn9bZwsT9YnnG18Ol+pkLQpBHkAISq99NhbVdYj5lAYED/n2qIJpP1ROLJlFMJQ4PvftjCmAHUHy0tnkxA==}
+  rusty-store-kv-win32-x64-msvc@1.3.1:
+    resolution: {integrity: sha512-CY1pmACrPg1mgfWPk7/dtB24TGc0RWv34+8Eg2lXbD6V7ePSMOVeVcIH7ra/JIjxbJJV2ljWvhkgUnEnp1FSKA==}
     engines: {node: '>= 10'}
     cpu: [x64]
     os: [win32]
 
-  rusty-store-kv@1.2.0:
-    resolution: {integrity: sha512-XlzClGSiIfOrpCUEh/eAAktFbsF4IPpDDeOEIP+cWmLkbVzWuqXVLIBouBH4ybKBlw+bQCyjOnZwBdyM9UVpUg==}
+  rusty-store-kv@1.3.1:
+    resolution: {integrity: sha512-Kk+55VwQ5qLWcSD6R0RrxFOEF70SH7BjYj60MCskJvRkuY7MFlAPEn3hY4WzRodWXj5cCOJ4AsDr+4OvtaW/SQ==}
     engines: {node: '>= 10'}
 
   safe-array-concat@1.1.2:
@@ -4051,15 +4080,15 @@ packages:
     engines: {node: '>=10'}
     hasBin: true
 
-  send@0.18.0:
-    resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==}
+  send@0.19.0:
+    resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==}
     engines: {node: '>= 0.8.0'}
 
   serialize-javascript@6.0.2:
     resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==}
 
-  serve-static@1.15.0:
-    resolution: {integrity: sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==}
+  serve-static@1.16.2:
+    resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==}
     engines: {node: '>= 0.8.0'}
 
   set-cookie-parser@2.7.0:
@@ -4097,12 +4126,8 @@ packages:
   signal-exit@3.0.7:
     resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
 
-  signal-exit@4.1.0:
-    resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
-    engines: {node: '>=14'}
-
-  sinon@18.0.0:
-    resolution: {integrity: sha512-+dXDXzD1sBO6HlmZDd7mXZCR/y5ECiEiGCBSGuFD/kZ0bDTofPYc6JaeGmPSF+1j1MejGUWkORbYOLDyvqCWpA==}
+  sinon@19.0.2:
+    resolution: {integrity: sha512-euuToqM+PjO4UgXeLETsfQiuoyPXlqFezr6YZDFwHR3t4qaX0fZUe1MfPMznTL5f8BWrVS89KduLdMUsxFCO6g==}
 
   slash@3.0.0:
     resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==}
@@ -4203,10 +4228,6 @@ packages:
     resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==}
     engines: {node: '>=4'}
 
-  strip-final-newline@3.0.0:
-    resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==}
-    engines: {node: '>=12'}
-
   strip-json-comments@3.1.1:
     resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
     engines: {node: '>=8'}
@@ -4254,6 +4275,15 @@ packages:
   swagger-schema-official@2.0.0-bab6bed:
     resolution: {integrity: sha512-rCC0NWGKr/IJhtRuPq/t37qvZHI/mH4I4sxflVM+qgVe5Z2uOCivzWaVbuioJaB61kvm5UvB7b49E+oBY0M8jA==}
 
+  swagger-ui-dist@5.17.14:
+    resolution: {integrity: sha512-CVbSfaLpstV65OnSjbXfVd6Sta3q3F7Cj/yYuvHMp1P90LztOLs6PfUnKEVAeiIVQt9u2SaPwv0LiH/OyMjHRw==}
+
+  swagger-ui-express@5.0.1:
+    resolution: {integrity: sha512-SrNU3RiBGTLLmFU8GIJdOdanJTl4TOmT27tt3bWWHppqYmAZ6IDuEuBvMU6nZq0zLEe6b/1rACXCgLZqO6ZfrA==}
+    engines: {node: '>= v0.10.32'}
+    peerDependencies:
+      express: '>=4.0.0 || >=5.0.0-beta'
+
   symbol-tree@3.2.4:
     resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
 
@@ -4277,6 +4307,9 @@ packages:
   tinycon@0.6.8:
     resolution: {integrity: sha512-bF8Lxm4JUXF6Cw0XlZdugJ44GV575OinZ0Pt8vQPr8ooNqd2yyNkoFdCHzmdpHlgoqfSLfcyk4HDP1EyllT+ug==}
 
+  tinyexec@0.3.0:
+    resolution: {integrity: sha512-tVGE0mVJPGb0chKhqmsoosjsS+qUnJVGJpZgsHYQcGoPlG3B51R3PouqTgEGH2Dc9jjFyOqOpix6ZHNMXp1FZg==}
+
   tinypool@1.0.0:
     resolution: {integrity: sha512-KIKExllK7jp3uvrNtvRBYBWBOAXSX8ZvoaD8T+7KB/QHIuoJW3Pmr60zucywjAlMb5TeXUkcs/MWeWLu0qvuAQ==}
     engines: {node: ^18.0.0 || >=20.0.0}
@@ -4331,8 +4364,8 @@ packages:
     resolution: {integrity: sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==}
     engines: {node: '>=0.6.x'}
 
-  tsx@4.19.0:
-    resolution: {integrity: sha512-bV30kM7bsLZKZIOCHeMNVMJ32/LuJzLVajkQI/qf92J2Qr08ueLQvW00PUZGiuLPP760UINwupgUj8qrSCPUKg==}
+  tsx@4.19.1:
+    resolution: {integrity: sha512-0flMz1lh74BR4wOvBjuh9olbnwqCPc35OOlfyzHba0Dc+QNUeWX/Gq2YTbnwcWPO3BMd8fkzRVrHcsR+a7z7rA==}
     engines: {node: '>=18.0.0'}
     hasBin: true
 
@@ -4344,6 +4377,10 @@ packages:
     resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==}
     engines: {node: '>=4'}
 
+  type-detect@4.1.0:
+    resolution: {integrity: sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==}
+    engines: {node: '>=4'}
+
   type-fest@0.20.2:
     resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==}
     engines: {node: '>=10'}
@@ -4368,13 +4405,13 @@ packages:
     resolution: {integrity: sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==}
     engines: {node: '>= 0.4'}
 
-  typescript@5.5.4:
-    resolution: {integrity: sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==}
+  typescript@5.6.2:
+    resolution: {integrity: sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==}
     engines: {node: '>=14.17'}
     hasBin: true
 
-  ueberdb2@4.2.103:
-    resolution: {integrity: sha512-jp+G38Za9vCKGRIGNEr28b7HOE0Et9J/txNSUrGnCGwpkkPLvqkX4tbx8sO3AVOynRuNMYfGy4cS0aoYt/2ePw==}
+  ueberdb2@5.0.2:
+    resolution: {integrity: sha512-dgrmxTxc/gkWuGK4UArTGwM6zBfG9hANBbhAxK+OUmZs3GatguyoBCp7LgNqenDMp3ElM/KyL6RBZ3OWpqTFKg==}
     engines: {node: '>=16.20.1'}
 
   uid-safe@2.1.5:
@@ -4485,8 +4522,8 @@ packages:
   vfile@6.0.2:
     resolution: {integrity: sha512-zND7NlS8rJYb/sPqkb13ZvbbUoExdbi4w3SfRrMq6R3FvnLQmmfpajJNITuuYm6AZ5uao9vy4BAos3EXBPf2rg==}
 
-  vite-node@2.0.5:
-    resolution: {integrity: sha512-LdsW4pxj0Ot69FAoXZ1yTnA9bjGohr2yNBU7QKRxpz8ITSkhuDl6h3zS/tvgz4qrNjeRnvrWeXQ8ZF7Um4W00Q==}
+  vite-node@2.1.1:
+    resolution: {integrity: sha512-N/mGckI1suG/5wQI35XeR9rsMsPqKXzq1CdUndzVstBj/HvyxxGctwnK6WX43NGt5L3Z5tcRf83g4TITKJhPrA==}
     engines: {node: ^18.0.0 || >=20.0.0}
     hasBin: true
 
@@ -4501,8 +4538,8 @@ packages:
     peerDependencies:
       vite: ^2.6.0 || 3 || 4 || 5
 
-  vite@5.4.3:
-    resolution: {integrity: sha512-IH+nl64eq9lJjFqU+/yrRnrHPVTlgy42/+IzbOdaFDVlyLgI/wDlf+FCobXLX1cT0X5+7LMyH1mIy2xJdLfo8Q==}
+  vite@5.4.7:
+    resolution: {integrity: sha512-5l2zxqMEPVENgvzTuBpHer2awaetimj2BGkhBPdnwKbPNOlHsODU+oiazEZzLK7KhAnOrO+XGYJYn4ZlUhDtDQ==}
     engines: {node: ^18.0.0 || >=20.0.0}
     hasBin: true
     peerDependencies:
@@ -4544,15 +4581,15 @@ packages:
       postcss:
         optional: true
 
-  vitest@2.0.5:
-    resolution: {integrity: sha512-8GUxONfauuIdeSl5f9GTgVEpg5BTOlplET4WEDaeY2QBiN8wSm68vxN/tb5z405OwppfoCavnwXafiaYBC/xOA==}
+  vitest@2.1.1:
+    resolution: {integrity: sha512-97We7/VC0e9X5zBVkvt7SGQMGrRtn3KtySFQG5fpaMlS+l62eeXRQO633AYhSTC3z7IMebnPPNjGXVGNRFlxBA==}
     engines: {node: ^18.0.0 || >=20.0.0}
     hasBin: true
     peerDependencies:
       '@edge-runtime/vm': '*'
       '@types/node': ^18.0.0 || >=20.0.0
-      '@vitest/browser': 2.0.5
-      '@vitest/ui': 2.0.5
+      '@vitest/browser': 2.1.1
+      '@vitest/ui': 2.1.1
       happy-dom: '*'
       jsdom: '*'
     peerDependenciesMeta:
@@ -4873,7 +4910,7 @@ snapshots:
       '@babel/traverse': 7.24.7
       '@babel/types': 7.24.7
       convert-source-map: 2.0.0
-      debug: 4.3.5(supports-color@8.1.1)
+      debug: 4.3.7
       gensync: 1.0.0-beta.2
       json5: 2.2.3
       semver: 6.3.1
@@ -4967,6 +5004,10 @@ snapshots:
     dependencies:
       regenerator-runtime: 0.14.1
 
+  '@babel/runtime@7.25.6':
+    dependencies:
+      regenerator-runtime: 0.14.1
+
   '@babel/template@7.24.7':
     dependencies:
       '@babel/code-frame': 7.24.7
@@ -4983,7 +5024,7 @@ snapshots:
       '@babel/helper-split-export-declaration': 7.24.7
       '@babel/parser': 7.24.7
       '@babel/types': 7.24.7
-      debug: 4.3.5(supports-color@8.1.1)
+      debug: 4.3.7
       globals: 11.12.0
     transitivePeerDependencies:
       - supports-color
@@ -4996,9 +5037,9 @@ snapshots:
 
   '@docsearch/css@3.6.1': {}
 
-  '@docsearch/js@3.6.1(@algolia/client-search@4.23.3)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+  '@docsearch/js@3.6.1(@algolia/client-search@4.23.3)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
     dependencies:
-      '@docsearch/react': 3.6.1(@algolia/client-search@4.23.3)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@docsearch/react': 3.6.1(@algolia/client-search@4.23.3)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
       preact: 10.22.0
     transitivePeerDependencies:
       - '@algolia/client-search'
@@ -5007,14 +5048,14 @@ snapshots:
       - react-dom
       - search-insights
 
-  '@docsearch/react@3.6.1(@algolia/client-search@4.23.3)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+  '@docsearch/react@3.6.1(@algolia/client-search@4.23.3)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
     dependencies:
       '@algolia/autocomplete-core': 1.9.3(@algolia/client-search@4.23.3)(algoliasearch@4.23.3)
       '@algolia/autocomplete-preset-algolia': 1.9.3(@algolia/client-search@4.23.3)(algoliasearch@4.23.3)
       '@docsearch/css': 3.6.1
       algoliasearch: 4.23.3
     optionalDependencies:
-      '@types/react': 18.3.5
+      '@types/react': 18.3.8
       react: 18.3.1
       react-dom: 18.3.1(react@18.3.1)
     transitivePeerDependencies:
@@ -5161,9 +5202,9 @@ snapshots:
   '@esbuild/win32-x64@0.23.1':
     optional: true
 
-  '@eslint-community/eslint-utils@4.4.0(eslint@9.9.1)':
+  '@eslint-community/eslint-utils@4.4.0(eslint@9.10.0)':
     dependencies:
-      eslint: 9.9.1
+      eslint: 9.10.0
       eslint-visitor-keys: 3.4.3
 
   '@eslint-community/regexpp@4.11.0': {}
@@ -5171,7 +5212,7 @@ snapshots:
   '@eslint/config-array@0.18.0':
     dependencies:
       '@eslint/object-schema': 2.1.4
-      debug: 4.3.5(supports-color@8.1.1)
+      debug: 4.3.7
       minimatch: 3.1.2
     transitivePeerDependencies:
       - supports-color
@@ -5179,7 +5220,7 @@ snapshots:
   '@eslint/eslintrc@3.1.0':
     dependencies:
       ajv: 6.12.6
-      debug: 4.3.5(supports-color@8.1.1)
+      debug: 4.3.7
       espree: 10.1.0
       globals: 14.0.0
       ignore: 5.3.1
@@ -5190,10 +5231,14 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
-  '@eslint/js@9.9.1': {}
+  '@eslint/js@9.10.0': {}
 
   '@eslint/object-schema@2.1.4': {}
 
+  '@eslint/plugin-kit@0.1.0':
+    dependencies:
+      levn: 0.4.1
+
   '@etherpad/express-session@1.18.4':
     dependencies:
       cookie: 0.4.2
@@ -5214,19 +5259,19 @@ snapshots:
   '@jridgewell/gen-mapping@0.3.5':
     dependencies:
       '@jridgewell/set-array': 1.2.1
-      '@jridgewell/sourcemap-codec': 1.4.15
+      '@jridgewell/sourcemap-codec': 1.5.0
       '@jridgewell/trace-mapping': 0.3.25
 
   '@jridgewell/resolve-uri@3.1.2': {}
 
   '@jridgewell/set-array@1.2.1': {}
 
-  '@jridgewell/sourcemap-codec@1.4.15': {}
+  '@jridgewell/sourcemap-codec@1.5.0': {}
 
   '@jridgewell/trace-mapping@0.3.25':
     dependencies:
       '@jridgewell/resolve-uri': 3.1.2
-      '@jridgewell/sourcemap-codec': 1.4.15
+      '@jridgewell/sourcemap-codec': 1.5.0
 
   '@jsdevtools/ono@7.1.3': {}
 
@@ -5236,7 +5281,7 @@ snapshots:
 
   '@koa/router@12.0.1':
     dependencies:
-      debug: 4.3.5(supports-color@8.1.1)
+      debug: 4.3.7
       http-errors: 2.0.0
       koa-compose: 4.1.0
       methods: 1.1.2
@@ -5256,215 +5301,215 @@ snapshots:
       '@nodelib/fs.scandir': 2.1.5
       fastq: 1.17.1
 
-  '@playwright/test@1.47.0':
+  '@playwright/test@1.47.1':
     dependencies:
-      playwright: 1.47.0
+      playwright: 1.47.1
 
   '@radix-ui/primitive@1.1.0': {}
 
-  '@radix-ui/react-collection@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+  '@radix-ui/react-collection@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
     dependencies:
-      '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.5)(react@18.3.1)
-      '@radix-ui/react-context': 1.1.0(@types/react@18.3.5)(react@18.3.1)
-      '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
-      '@radix-ui/react-slot': 1.1.0(@types/react@18.3.5)(react@18.3.1)
+      '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.8)(react@18.3.1)
+      '@radix-ui/react-context': 1.1.0(@types/react@18.3.8)(react@18.3.1)
+      '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-slot': 1.1.0(@types/react@18.3.8)(react@18.3.1)
       react: 18.3.1
       react-dom: 18.3.1(react@18.3.1)
     optionalDependencies:
-      '@types/react': 18.3.5
+      '@types/react': 18.3.8
       '@types/react-dom': 18.3.0
 
-  '@radix-ui/react-compose-refs@1.1.0(@types/react@18.3.5)(react@18.3.1)':
+  '@radix-ui/react-compose-refs@1.1.0(@types/react@18.3.8)(react@18.3.1)':
     dependencies:
       react: 18.3.1
     optionalDependencies:
-      '@types/react': 18.3.5
+      '@types/react': 18.3.8
 
-  '@radix-ui/react-context@1.1.0(@types/react@18.3.5)(react@18.3.1)':
+  '@radix-ui/react-context@1.1.0(@types/react@18.3.8)(react@18.3.1)':
     dependencies:
       react: 18.3.1
     optionalDependencies:
-      '@types/react': 18.3.5
+      '@types/react': 18.3.8
 
-  '@radix-ui/react-dialog@1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+  '@radix-ui/react-dialog@1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
     dependencies:
       '@radix-ui/primitive': 1.1.0
-      '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.5)(react@18.3.1)
-      '@radix-ui/react-context': 1.1.0(@types/react@18.3.5)(react@18.3.1)
-      '@radix-ui/react-dismissable-layer': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
-      '@radix-ui/react-focus-guards': 1.1.0(@types/react@18.3.5)(react@18.3.1)
-      '@radix-ui/react-focus-scope': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
-      '@radix-ui/react-id': 1.1.0(@types/react@18.3.5)(react@18.3.1)
-      '@radix-ui/react-portal': 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
-      '@radix-ui/react-presence': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
-      '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
-      '@radix-ui/react-slot': 1.1.0(@types/react@18.3.5)(react@18.3.1)
-      '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.5)(react@18.3.1)
+      '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.8)(react@18.3.1)
+      '@radix-ui/react-context': 1.1.0(@types/react@18.3.8)(react@18.3.1)
+      '@radix-ui/react-dismissable-layer': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-focus-guards': 1.1.0(@types/react@18.3.8)(react@18.3.1)
+      '@radix-ui/react-focus-scope': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-id': 1.1.0(@types/react@18.3.8)(react@18.3.1)
+      '@radix-ui/react-portal': 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-presence': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-slot': 1.1.0(@types/react@18.3.8)(react@18.3.1)
+      '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.8)(react@18.3.1)
       aria-hidden: 1.2.4
       react: 18.3.1
       react-dom: 18.3.1(react@18.3.1)
-      react-remove-scroll: 2.5.7(@types/react@18.3.5)(react@18.3.1)
+      react-remove-scroll: 2.5.7(@types/react@18.3.8)(react@18.3.1)
     optionalDependencies:
-      '@types/react': 18.3.5
+      '@types/react': 18.3.8
       '@types/react-dom': 18.3.0
 
-  '@radix-ui/react-dismissable-layer@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+  '@radix-ui/react-dismissable-layer@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
     dependencies:
       '@radix-ui/primitive': 1.1.0
-      '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.5)(react@18.3.1)
-      '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
-      '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.5)(react@18.3.1)
-      '@radix-ui/react-use-escape-keydown': 1.1.0(@types/react@18.3.5)(react@18.3.1)
+      '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.8)(react@18.3.1)
+      '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.8)(react@18.3.1)
+      '@radix-ui/react-use-escape-keydown': 1.1.0(@types/react@18.3.8)(react@18.3.1)
       react: 18.3.1
       react-dom: 18.3.1(react@18.3.1)
     optionalDependencies:
-      '@types/react': 18.3.5
+      '@types/react': 18.3.8
       '@types/react-dom': 18.3.0
 
-  '@radix-ui/react-focus-guards@1.1.0(@types/react@18.3.5)(react@18.3.1)':
+  '@radix-ui/react-focus-guards@1.1.0(@types/react@18.3.8)(react@18.3.1)':
     dependencies:
       react: 18.3.1
     optionalDependencies:
-      '@types/react': 18.3.5
+      '@types/react': 18.3.8
 
-  '@radix-ui/react-focus-scope@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+  '@radix-ui/react-focus-scope@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
     dependencies:
-      '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.5)(react@18.3.1)
-      '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
-      '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.5)(react@18.3.1)
+      '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.8)(react@18.3.1)
+      '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.8)(react@18.3.1)
       react: 18.3.1
       react-dom: 18.3.1(react@18.3.1)
     optionalDependencies:
-      '@types/react': 18.3.5
+      '@types/react': 18.3.8
       '@types/react-dom': 18.3.0
 
-  '@radix-ui/react-id@1.1.0(@types/react@18.3.5)(react@18.3.1)':
+  '@radix-ui/react-id@1.1.0(@types/react@18.3.8)(react@18.3.1)':
     dependencies:
-      '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.5)(react@18.3.1)
+      '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.8)(react@18.3.1)
       react: 18.3.1
     optionalDependencies:
-      '@types/react': 18.3.5
+      '@types/react': 18.3.8
 
-  '@radix-ui/react-portal@1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+  '@radix-ui/react-portal@1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
     dependencies:
-      '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
-      '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.5)(react@18.3.1)
+      '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.8)(react@18.3.1)
       react: 18.3.1
       react-dom: 18.3.1(react@18.3.1)
     optionalDependencies:
-      '@types/react': 18.3.5
+      '@types/react': 18.3.8
       '@types/react-dom': 18.3.0
 
-  '@radix-ui/react-presence@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+  '@radix-ui/react-presence@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
     dependencies:
-      '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.5)(react@18.3.1)
-      '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.5)(react@18.3.1)
+      '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.8)(react@18.3.1)
+      '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.8)(react@18.3.1)
       react: 18.3.1
       react-dom: 18.3.1(react@18.3.1)
     optionalDependencies:
-      '@types/react': 18.3.5
+      '@types/react': 18.3.8
       '@types/react-dom': 18.3.0
 
-  '@radix-ui/react-primitive@2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+  '@radix-ui/react-primitive@2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
     dependencies:
-      '@radix-ui/react-slot': 1.1.0(@types/react@18.3.5)(react@18.3.1)
+      '@radix-ui/react-slot': 1.1.0(@types/react@18.3.8)(react@18.3.1)
       react: 18.3.1
       react-dom: 18.3.1(react@18.3.1)
     optionalDependencies:
-      '@types/react': 18.3.5
+      '@types/react': 18.3.8
       '@types/react-dom': 18.3.0
 
-  '@radix-ui/react-slot@1.1.0(@types/react@18.3.5)(react@18.3.1)':
+  '@radix-ui/react-slot@1.1.0(@types/react@18.3.8)(react@18.3.1)':
     dependencies:
-      '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.5)(react@18.3.1)
+      '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.8)(react@18.3.1)
       react: 18.3.1
     optionalDependencies:
-      '@types/react': 18.3.5
+      '@types/react': 18.3.8
 
-  '@radix-ui/react-switch@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+  '@radix-ui/react-switch@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
     dependencies:
       '@radix-ui/primitive': 1.1.0
-      '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.5)(react@18.3.1)
-      '@radix-ui/react-context': 1.1.0(@types/react@18.3.5)(react@18.3.1)
-      '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
-      '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.5)(react@18.3.1)
-      '@radix-ui/react-use-previous': 1.1.0(@types/react@18.3.5)(react@18.3.1)
-      '@radix-ui/react-use-size': 1.1.0(@types/react@18.3.5)(react@18.3.1)
+      '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.8)(react@18.3.1)
+      '@radix-ui/react-context': 1.1.0(@types/react@18.3.8)(react@18.3.1)
+      '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.8)(react@18.3.1)
+      '@radix-ui/react-use-previous': 1.1.0(@types/react@18.3.8)(react@18.3.1)
+      '@radix-ui/react-use-size': 1.1.0(@types/react@18.3.8)(react@18.3.1)
       react: 18.3.1
       react-dom: 18.3.1(react@18.3.1)
     optionalDependencies:
-      '@types/react': 18.3.5
+      '@types/react': 18.3.8
       '@types/react-dom': 18.3.0
 
-  '@radix-ui/react-toast@1.2.1(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+  '@radix-ui/react-toast@1.2.1(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
     dependencies:
       '@radix-ui/primitive': 1.1.0
-      '@radix-ui/react-collection': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
-      '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.5)(react@18.3.1)
-      '@radix-ui/react-context': 1.1.0(@types/react@18.3.5)(react@18.3.1)
-      '@radix-ui/react-dismissable-layer': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
-      '@radix-ui/react-portal': 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
-      '@radix-ui/react-presence': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
-      '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
-      '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.5)(react@18.3.1)
-      '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.5)(react@18.3.1)
-      '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.5)(react@18.3.1)
-      '@radix-ui/react-visually-hidden': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-collection': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.8)(react@18.3.1)
+      '@radix-ui/react-context': 1.1.0(@types/react@18.3.8)(react@18.3.1)
+      '@radix-ui/react-dismissable-layer': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-portal': 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-presence': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.8)(react@18.3.1)
+      '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.8)(react@18.3.1)
+      '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.8)(react@18.3.1)
+      '@radix-ui/react-visually-hidden': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
       react: 18.3.1
       react-dom: 18.3.1(react@18.3.1)
     optionalDependencies:
-      '@types/react': 18.3.5
+      '@types/react': 18.3.8
       '@types/react-dom': 18.3.0
 
-  '@radix-ui/react-use-callback-ref@1.1.0(@types/react@18.3.5)(react@18.3.1)':
+  '@radix-ui/react-use-callback-ref@1.1.0(@types/react@18.3.8)(react@18.3.1)':
     dependencies:
       react: 18.3.1
     optionalDependencies:
-      '@types/react': 18.3.5
+      '@types/react': 18.3.8
 
-  '@radix-ui/react-use-controllable-state@1.1.0(@types/react@18.3.5)(react@18.3.1)':
+  '@radix-ui/react-use-controllable-state@1.1.0(@types/react@18.3.8)(react@18.3.1)':
     dependencies:
-      '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.5)(react@18.3.1)
+      '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.8)(react@18.3.1)
       react: 18.3.1
     optionalDependencies:
-      '@types/react': 18.3.5
+      '@types/react': 18.3.8
 
-  '@radix-ui/react-use-escape-keydown@1.1.0(@types/react@18.3.5)(react@18.3.1)':
+  '@radix-ui/react-use-escape-keydown@1.1.0(@types/react@18.3.8)(react@18.3.1)':
     dependencies:
-      '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.5)(react@18.3.1)
+      '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.8)(react@18.3.1)
       react: 18.3.1
     optionalDependencies:
-      '@types/react': 18.3.5
+      '@types/react': 18.3.8
 
-  '@radix-ui/react-use-layout-effect@1.1.0(@types/react@18.3.5)(react@18.3.1)':
+  '@radix-ui/react-use-layout-effect@1.1.0(@types/react@18.3.8)(react@18.3.1)':
     dependencies:
       react: 18.3.1
     optionalDependencies:
-      '@types/react': 18.3.5
+      '@types/react': 18.3.8
 
-  '@radix-ui/react-use-previous@1.1.0(@types/react@18.3.5)(react@18.3.1)':
+  '@radix-ui/react-use-previous@1.1.0(@types/react@18.3.8)(react@18.3.1)':
     dependencies:
       react: 18.3.1
     optionalDependencies:
-      '@types/react': 18.3.5
+      '@types/react': 18.3.8
 
-  '@radix-ui/react-use-size@1.1.0(@types/react@18.3.5)(react@18.3.1)':
+  '@radix-ui/react-use-size@1.1.0(@types/react@18.3.8)(react@18.3.1)':
     dependencies:
-      '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.5)(react@18.3.1)
+      '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.8)(react@18.3.1)
       react: 18.3.1
     optionalDependencies:
-      '@types/react': 18.3.5
+      '@types/react': 18.3.8
 
-  '@radix-ui/react-visually-hidden@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+  '@radix-ui/react-visually-hidden@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
     dependencies:
-      '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
       react: 18.3.1
       react-dom: 18.3.1(react@18.3.1)
     optionalDependencies:
-      '@types/react': 18.3.5
+      '@types/react': 18.3.8
       '@types/react-dom': 18.3.0
 
-  '@remix-run/router@1.19.1': {}
+  '@remix-run/router@1.19.2': {}
 
   '@rollup/pluginutils@5.1.0(rollup@4.21.0)':
     dependencies:
@@ -5534,25 +5579,21 @@ snapshots:
 
   '@sindresorhus/is@5.6.0': {}
 
-  '@sinonjs/commons@2.0.0':
-    dependencies:
-      type-detect: 4.0.8
-
   '@sinonjs/commons@3.0.1':
     dependencies:
       type-detect: 4.0.8
 
-  '@sinonjs/fake-timers@11.2.2':
+  '@sinonjs/fake-timers@13.0.2':
     dependencies:
       '@sinonjs/commons': 3.0.1
 
-  '@sinonjs/samsam@8.0.0':
+  '@sinonjs/samsam@8.0.2':
     dependencies:
-      '@sinonjs/commons': 2.0.0
+      '@sinonjs/commons': 3.0.1
       lodash.get: 4.4.2
-      type-detect: 4.0.8
+      type-detect: 4.1.0
 
-  '@sinonjs/text-encoding@0.7.2': {}
+  '@sinonjs/text-encoding@0.7.3': {}
 
   '@socket.io/component-emitter@3.1.2': {}
 
@@ -5600,12 +5641,12 @@ snapshots:
       '@svgr/babel-plugin-transform-react-native-svg': 8.1.0(@babel/core@7.24.7)
       '@svgr/babel-plugin-transform-svg-component': 8.0.0(@babel/core@7.24.7)
 
-  '@svgr/core@8.1.0(typescript@5.5.4)':
+  '@svgr/core@8.1.0(typescript@5.6.2)':
     dependencies:
       '@babel/core': 7.24.7
       '@svgr/babel-preset': 8.1.0(@babel/core@7.24.7)
       camelcase: 6.3.0
-      cosmiconfig: 8.3.6(typescript@5.5.4)
+      cosmiconfig: 8.3.6(typescript@5.6.2)
       snake-case: 3.0.4
     transitivePeerDependencies:
       - supports-color
@@ -5616,11 +5657,11 @@ snapshots:
       '@babel/types': 7.24.7
       entities: 4.5.0
 
-  '@svgr/plugin-jsx@8.1.0(@svgr/core@8.1.0(typescript@5.5.4))':
+  '@svgr/plugin-jsx@8.1.0(@svgr/core@8.1.0(typescript@5.6.2))':
     dependencies:
       '@babel/core': 7.24.7
       '@svgr/babel-preset': 8.1.0(@babel/core@7.24.7)
-      '@svgr/core': 8.1.0(typescript@5.5.4)
+      '@svgr/core': 8.1.0(typescript@5.6.2)
       '@svgr/hast-util-to-babel-ast': 8.0.0
       svg-parser: 2.0.4
     transitivePeerDependencies:
@@ -5686,18 +5727,18 @@ snapshots:
 
   '@types/accepts@1.3.7':
     dependencies:
-      '@types/node': 22.5.4
+      '@types/node': 22.5.5
 
   '@types/async@3.2.24': {}
 
   '@types/body-parser@1.19.5':
     dependencies:
       '@types/connect': 3.4.38
-      '@types/node': 22.5.4
+      '@types/node': 22.5.5
 
   '@types/connect@3.4.38':
     dependencies:
-      '@types/node': 22.5.4
+      '@types/node': 22.5.5
 
   '@types/content-disposition@0.5.8': {}
 
@@ -5710,11 +5751,11 @@ snapshots:
       '@types/connect': 3.4.38
       '@types/express': 4.17.21
       '@types/keygrip': 1.0.6
-      '@types/node': 22.5.4
+      '@types/node': 22.5.5
 
   '@types/cors@2.8.17':
     dependencies:
-      '@types/node': 22.5.4
+      '@types/node': 22.5.5
 
   '@types/debug@4.1.12':
     dependencies:
@@ -5724,7 +5765,7 @@ snapshots:
 
   '@types/express-serve-static-core@4.19.5':
     dependencies:
-      '@types/node': 22.5.4
+      '@types/node': 22.5.5
       '@types/qs': 6.9.15
       '@types/range-parser': 1.2.7
       '@types/send': 0.17.4
@@ -5738,11 +5779,11 @@ snapshots:
 
   '@types/formidable@3.4.5':
     dependencies:
-      '@types/node': 22.5.4
+      '@types/node': 22.5.5
 
   '@types/fs-extra@9.0.13':
     dependencies:
-      '@types/node': 22.5.4
+      '@types/node': 22.5.5
 
   '@types/hast@3.0.4':
     dependencies:
@@ -5762,7 +5803,7 @@ snapshots:
 
   '@types/jsdom@21.1.7':
     dependencies:
-      '@types/node': 22.5.4
+      '@types/node': 22.5.5
       '@types/tough-cookie': 4.0.5
       parse5: 7.1.2
 
@@ -5770,9 +5811,9 @@ snapshots:
 
   '@types/json5@0.0.29': {}
 
-  '@types/jsonwebtoken@9.0.6':
+  '@types/jsonwebtoken@9.0.7':
     dependencies:
-      '@types/node': 22.5.4
+      '@types/node': 22.5.5
 
   '@types/keygrip@1.0.6': {}
 
@@ -5789,7 +5830,7 @@ snapshots:
       '@types/http-errors': 2.0.4
       '@types/keygrip': 1.0.6
       '@types/koa-compose': 3.2.8
-      '@types/node': 22.5.4
+      '@types/node': 22.5.5
 
   '@types/linkify-it@5.0.0': {}
 
@@ -5812,23 +5853,23 @@ snapshots:
 
   '@types/mime@1.3.5': {}
 
-  '@types/mocha@10.0.7': {}
+  '@types/mocha@10.0.8': {}
 
   '@types/ms@0.7.34': {}
 
   '@types/node-fetch@2.6.11':
     dependencies:
-      '@types/node': 22.5.4
+      '@types/node': 22.5.5
       form-data: 4.0.0
 
-  '@types/node@22.5.4':
+  '@types/node@22.5.5':
     dependencies:
       undici-types: 6.19.6
 
   '@types/oidc-provider@8.5.2':
     dependencies:
       '@types/koa': 2.15.0
-      '@types/node': 22.5.4
+      '@types/node': 22.5.5
 
   '@types/prop-types@15.7.12': {}
 
@@ -5838,9 +5879,9 @@ snapshots:
 
   '@types/react-dom@18.3.0':
     dependencies:
-      '@types/react': 18.3.5
+      '@types/react': 18.3.8
 
-  '@types/react@18.3.5':
+  '@types/react@18.3.8':
     dependencies:
       '@types/prop-types': 15.7.12
       csstype: 3.1.3
@@ -5850,12 +5891,12 @@ snapshots:
   '@types/send@0.17.4':
     dependencies:
       '@types/mime': 1.3.5
-      '@types/node': 22.5.4
+      '@types/node': 22.5.5
 
   '@types/serve-static@1.15.7':
     dependencies:
       '@types/http-errors': 2.0.4
-      '@types/node': 22.5.4
+      '@types/node': 22.5.5
       '@types/send': 0.17.4
 
   '@types/sinon@17.0.3':
@@ -5870,16 +5911,21 @@ snapshots:
     dependencies:
       '@types/cookiejar': 2.1.5
       '@types/methods': 1.1.4
-      '@types/node': 22.5.4
+      '@types/node': 22.5.5
 
   '@types/supertest@6.0.2':
     dependencies:
       '@types/methods': 1.1.4
       '@types/superagent': 8.1.7
 
+  '@types/swagger-ui-express@4.1.6':
+    dependencies:
+      '@types/express': 4.17.21
+      '@types/serve-static': 1.15.7
+
   '@types/tar@6.1.13':
     dependencies:
-      '@types/node': 22.5.4
+      '@types/node': 22.5.5
       minipass: 4.2.8
 
   '@types/tough-cookie@4.0.5': {}
@@ -5892,65 +5938,67 @@ snapshots:
 
   '@types/web-bluetooth@0.0.20': {}
 
-  '@typescript-eslint/eslint-plugin@7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.9.1)(typescript@5.5.4))(eslint@9.9.1)(typescript@5.5.4)':
+  '@types/whatwg-mimetype@3.0.2': {}
+
+  '@typescript-eslint/eslint-plugin@7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.10.0)(typescript@5.6.2))(eslint@9.10.0)(typescript@5.6.2)':
     dependencies:
       '@eslint-community/regexpp': 4.11.0
-      '@typescript-eslint/parser': 7.17.0(eslint@9.9.1)(typescript@5.5.4)
+      '@typescript-eslint/parser': 7.17.0(eslint@9.10.0)(typescript@5.6.2)
       '@typescript-eslint/scope-manager': 7.17.0
-      '@typescript-eslint/type-utils': 7.17.0(eslint@9.9.1)(typescript@5.5.4)
-      '@typescript-eslint/utils': 7.17.0(eslint@9.9.1)(typescript@5.5.4)
+      '@typescript-eslint/type-utils': 7.17.0(eslint@9.10.0)(typescript@5.6.2)
+      '@typescript-eslint/utils': 7.17.0(eslint@9.10.0)(typescript@5.6.2)
       '@typescript-eslint/visitor-keys': 7.17.0
-      eslint: 9.9.1
+      eslint: 9.10.0
       graphemer: 1.4.0
       ignore: 5.3.1
       natural-compare: 1.4.0
-      ts-api-utils: 1.3.0(typescript@5.5.4)
+      ts-api-utils: 1.3.0(typescript@5.6.2)
     optionalDependencies:
-      typescript: 5.5.4
+      typescript: 5.6.2
     transitivePeerDependencies:
       - supports-color
 
-  '@typescript-eslint/eslint-plugin@8.4.0(@typescript-eslint/parser@8.4.0(eslint@9.9.1)(typescript@5.5.4))(eslint@9.9.1)(typescript@5.5.4)':
+  '@typescript-eslint/eslint-plugin@8.6.0(@typescript-eslint/parser@8.6.0(eslint@9.10.0)(typescript@5.6.2))(eslint@9.10.0)(typescript@5.6.2)':
     dependencies:
       '@eslint-community/regexpp': 4.11.0
-      '@typescript-eslint/parser': 8.4.0(eslint@9.9.1)(typescript@5.5.4)
-      '@typescript-eslint/scope-manager': 8.4.0
-      '@typescript-eslint/type-utils': 8.4.0(eslint@9.9.1)(typescript@5.5.4)
-      '@typescript-eslint/utils': 8.4.0(eslint@9.9.1)(typescript@5.5.4)
-      '@typescript-eslint/visitor-keys': 8.4.0
-      eslint: 9.9.1
+      '@typescript-eslint/parser': 8.6.0(eslint@9.10.0)(typescript@5.6.2)
+      '@typescript-eslint/scope-manager': 8.6.0
+      '@typescript-eslint/type-utils': 8.6.0(eslint@9.10.0)(typescript@5.6.2)
+      '@typescript-eslint/utils': 8.6.0(eslint@9.10.0)(typescript@5.6.2)
+      '@typescript-eslint/visitor-keys': 8.6.0
+      eslint: 9.10.0
       graphemer: 1.4.0
       ignore: 5.3.1
       natural-compare: 1.4.0
-      ts-api-utils: 1.3.0(typescript@5.5.4)
+      ts-api-utils: 1.3.0(typescript@5.6.2)
     optionalDependencies:
-      typescript: 5.5.4
+      typescript: 5.6.2
     transitivePeerDependencies:
       - supports-color
 
-  '@typescript-eslint/parser@7.17.0(eslint@9.9.1)(typescript@5.5.4)':
+  '@typescript-eslint/parser@7.17.0(eslint@9.10.0)(typescript@5.6.2)':
     dependencies:
       '@typescript-eslint/scope-manager': 7.17.0
       '@typescript-eslint/types': 7.17.0
-      '@typescript-eslint/typescript-estree': 7.17.0(typescript@5.5.4)
+      '@typescript-eslint/typescript-estree': 7.17.0(typescript@5.6.2)
       '@typescript-eslint/visitor-keys': 7.17.0
-      debug: 4.3.5(supports-color@8.1.1)
-      eslint: 9.9.1
+      debug: 4.3.7
+      eslint: 9.10.0
     optionalDependencies:
-      typescript: 5.5.4
+      typescript: 5.6.2
     transitivePeerDependencies:
       - supports-color
 
-  '@typescript-eslint/parser@8.4.0(eslint@9.9.1)(typescript@5.5.4)':
+  '@typescript-eslint/parser@8.6.0(eslint@9.10.0)(typescript@5.6.2)':
     dependencies:
-      '@typescript-eslint/scope-manager': 8.4.0
-      '@typescript-eslint/types': 8.4.0
-      '@typescript-eslint/typescript-estree': 8.4.0(typescript@5.5.4)
-      '@typescript-eslint/visitor-keys': 8.4.0
-      debug: 4.3.5(supports-color@8.1.1)
-      eslint: 9.9.1
+      '@typescript-eslint/scope-manager': 8.6.0
+      '@typescript-eslint/types': 8.6.0
+      '@typescript-eslint/typescript-estree': 8.6.0(typescript@5.6.2)
+      '@typescript-eslint/visitor-keys': 8.6.0
+      debug: 4.3.7
+      eslint: 9.10.0
     optionalDependencies:
-      typescript: 5.5.4
+      typescript: 5.6.2
     transitivePeerDependencies:
       - supports-color
 
@@ -5959,87 +6007,87 @@ snapshots:
       '@typescript-eslint/types': 7.17.0
       '@typescript-eslint/visitor-keys': 7.17.0
 
-  '@typescript-eslint/scope-manager@8.4.0':
+  '@typescript-eslint/scope-manager@8.6.0':
     dependencies:
-      '@typescript-eslint/types': 8.4.0
-      '@typescript-eslint/visitor-keys': 8.4.0
+      '@typescript-eslint/types': 8.6.0
+      '@typescript-eslint/visitor-keys': 8.6.0
 
-  '@typescript-eslint/type-utils@7.17.0(eslint@9.9.1)(typescript@5.5.4)':
+  '@typescript-eslint/type-utils@7.17.0(eslint@9.10.0)(typescript@5.6.2)':
     dependencies:
-      '@typescript-eslint/typescript-estree': 7.17.0(typescript@5.5.4)
-      '@typescript-eslint/utils': 7.17.0(eslint@9.9.1)(typescript@5.5.4)
-      debug: 4.3.5(supports-color@8.1.1)
-      eslint: 9.9.1
-      ts-api-utils: 1.3.0(typescript@5.5.4)
+      '@typescript-eslint/typescript-estree': 7.17.0(typescript@5.6.2)
+      '@typescript-eslint/utils': 7.17.0(eslint@9.10.0)(typescript@5.6.2)
+      debug: 4.3.7
+      eslint: 9.10.0
+      ts-api-utils: 1.3.0(typescript@5.6.2)
     optionalDependencies:
-      typescript: 5.5.4
+      typescript: 5.6.2
     transitivePeerDependencies:
       - supports-color
 
-  '@typescript-eslint/type-utils@8.4.0(eslint@9.9.1)(typescript@5.5.4)':
+  '@typescript-eslint/type-utils@8.6.0(eslint@9.10.0)(typescript@5.6.2)':
     dependencies:
-      '@typescript-eslint/typescript-estree': 8.4.0(typescript@5.5.4)
-      '@typescript-eslint/utils': 8.4.0(eslint@9.9.1)(typescript@5.5.4)
-      debug: 4.3.5(supports-color@8.1.1)
-      ts-api-utils: 1.3.0(typescript@5.5.4)
+      '@typescript-eslint/typescript-estree': 8.6.0(typescript@5.6.2)
+      '@typescript-eslint/utils': 8.6.0(eslint@9.10.0)(typescript@5.6.2)
+      debug: 4.3.7
+      ts-api-utils: 1.3.0(typescript@5.6.2)
     optionalDependencies:
-      typescript: 5.5.4
+      typescript: 5.6.2
     transitivePeerDependencies:
       - eslint
       - supports-color
 
   '@typescript-eslint/types@7.17.0': {}
 
-  '@typescript-eslint/types@8.4.0': {}
+  '@typescript-eslint/types@8.6.0': {}
 
-  '@typescript-eslint/typescript-estree@7.17.0(typescript@5.5.4)':
+  '@typescript-eslint/typescript-estree@7.17.0(typescript@5.6.2)':
     dependencies:
       '@typescript-eslint/types': 7.17.0
       '@typescript-eslint/visitor-keys': 7.17.0
-      debug: 4.3.5(supports-color@8.1.1)
+      debug: 4.3.7
       globby: 11.1.0
       is-glob: 4.0.3
       minimatch: 9.0.5
       semver: 7.6.3
-      ts-api-utils: 1.3.0(typescript@5.5.4)
+      ts-api-utils: 1.3.0(typescript@5.6.2)
     optionalDependencies:
-      typescript: 5.5.4
+      typescript: 5.6.2
     transitivePeerDependencies:
       - supports-color
 
-  '@typescript-eslint/typescript-estree@8.4.0(typescript@5.5.4)':
+  '@typescript-eslint/typescript-estree@8.6.0(typescript@5.6.2)':
     dependencies:
-      '@typescript-eslint/types': 8.4.0
-      '@typescript-eslint/visitor-keys': 8.4.0
-      debug: 4.3.5(supports-color@8.1.1)
+      '@typescript-eslint/types': 8.6.0
+      '@typescript-eslint/visitor-keys': 8.6.0
+      debug: 4.3.7
       fast-glob: 3.3.2
       is-glob: 4.0.3
       minimatch: 9.0.5
       semver: 7.6.3
-      ts-api-utils: 1.3.0(typescript@5.5.4)
+      ts-api-utils: 1.3.0(typescript@5.6.2)
     optionalDependencies:
-      typescript: 5.5.4
+      typescript: 5.6.2
     transitivePeerDependencies:
       - supports-color
 
-  '@typescript-eslint/utils@7.17.0(eslint@9.9.1)(typescript@5.5.4)':
+  '@typescript-eslint/utils@7.17.0(eslint@9.10.0)(typescript@5.6.2)':
     dependencies:
-      '@eslint-community/eslint-utils': 4.4.0(eslint@9.9.1)
+      '@eslint-community/eslint-utils': 4.4.0(eslint@9.10.0)
       '@typescript-eslint/scope-manager': 7.17.0
       '@typescript-eslint/types': 7.17.0
-      '@typescript-eslint/typescript-estree': 7.17.0(typescript@5.5.4)
-      eslint: 9.9.1
+      '@typescript-eslint/typescript-estree': 7.17.0(typescript@5.6.2)
+      eslint: 9.10.0
     transitivePeerDependencies:
       - supports-color
       - typescript
 
-  '@typescript-eslint/utils@8.4.0(eslint@9.9.1)(typescript@5.5.4)':
+  '@typescript-eslint/utils@8.6.0(eslint@9.10.0)(typescript@5.6.2)':
     dependencies:
-      '@eslint-community/eslint-utils': 4.4.0(eslint@9.9.1)
-      '@typescript-eslint/scope-manager': 8.4.0
-      '@typescript-eslint/types': 8.4.0
-      '@typescript-eslint/typescript-estree': 8.4.0(typescript@5.5.4)
-      eslint: 9.9.1
+      '@eslint-community/eslint-utils': 4.4.0(eslint@9.10.0)
+      '@typescript-eslint/scope-manager': 8.6.0
+      '@typescript-eslint/types': 8.6.0
+      '@typescript-eslint/typescript-estree': 8.6.0(typescript@5.6.2)
+      eslint: 9.10.0
     transitivePeerDependencies:
       - supports-color
       - typescript
@@ -6049,55 +6097,62 @@ snapshots:
       '@typescript-eslint/types': 7.17.0
       eslint-visitor-keys: 3.4.3
 
-  '@typescript-eslint/visitor-keys@8.4.0':
+  '@typescript-eslint/visitor-keys@8.6.0':
     dependencies:
-      '@typescript-eslint/types': 8.4.0
+      '@typescript-eslint/types': 8.6.0
       eslint-visitor-keys: 3.4.3
 
   '@ungap/structured-clone@1.2.0': {}
 
-  '@vitejs/plugin-react-swc@3.7.0(vite@5.4.3(@types/node@22.5.4))':
+  '@vitejs/plugin-react-swc@3.7.0(vite@5.4.7(@types/node@22.5.5))':
     dependencies:
       '@swc/core': 1.5.28
-      vite: 5.4.3(@types/node@22.5.4)
+      vite: 5.4.7(@types/node@22.5.5)
     transitivePeerDependencies:
       - '@swc/helpers'
 
-  '@vitejs/plugin-vue@5.1.2(vite@5.4.3(@types/node@22.5.4))(vue@3.4.38(typescript@5.5.4))':
+  '@vitejs/plugin-vue@5.1.2(vite@5.4.7(@types/node@22.5.5))(vue@3.4.38(typescript@5.6.2))':
     dependencies:
-      vite: 5.4.3(@types/node@22.5.4)
-      vue: 3.4.38(typescript@5.5.4)
+      vite: 5.4.7(@types/node@22.5.5)
+      vue: 3.4.38(typescript@5.6.2)
 
-  '@vitest/expect@2.0.5':
+  '@vitest/expect@2.1.1':
     dependencies:
-      '@vitest/spy': 2.0.5
-      '@vitest/utils': 2.0.5
+      '@vitest/spy': 2.1.1
+      '@vitest/utils': 2.1.1
       chai: 5.1.1
       tinyrainbow: 1.2.0
 
-  '@vitest/pretty-format@2.0.5':
+  '@vitest/mocker@2.1.1(vite@5.4.7(@types/node@22.5.5))':
+    dependencies:
+      '@vitest/spy': 2.1.1
+      estree-walker: 3.0.3
+      magic-string: 0.30.11
+    optionalDependencies:
+      vite: 5.4.7(@types/node@22.5.5)
+
+  '@vitest/pretty-format@2.1.1':
     dependencies:
       tinyrainbow: 1.2.0
 
-  '@vitest/runner@2.0.5':
+  '@vitest/runner@2.1.1':
     dependencies:
-      '@vitest/utils': 2.0.5
+      '@vitest/utils': 2.1.1
       pathe: 1.1.2
 
-  '@vitest/snapshot@2.0.5':
+  '@vitest/snapshot@2.1.1':
     dependencies:
-      '@vitest/pretty-format': 2.0.5
-      magic-string: 0.30.10
+      '@vitest/pretty-format': 2.1.1
+      magic-string: 0.30.11
       pathe: 1.1.2
 
-  '@vitest/spy@2.0.5':
+  '@vitest/spy@2.1.1':
     dependencies:
       tinyspy: 3.0.0
 
-  '@vitest/utils@2.0.5':
+  '@vitest/utils@2.1.1':
     dependencies:
-      '@vitest/pretty-format': 2.0.5
-      estree-walker: 3.0.3
+      '@vitest/pretty-format': 2.1.1
       loupe: 3.1.1
       tinyrainbow: 1.2.0
 
@@ -6122,7 +6177,7 @@ snapshots:
       '@vue/compiler-ssr': 3.4.38
       '@vue/shared': 3.4.38
       estree-walker: 2.0.2
-      magic-string: 0.30.10
+      magic-string: 0.30.11
       postcss: 8.4.45
       source-map-js: 1.2.0
 
@@ -6165,29 +6220,29 @@ snapshots:
       '@vue/shared': 3.4.38
       csstype: 3.1.3
 
-  '@vue/server-renderer@3.4.38(vue@3.4.38(typescript@5.5.4))':
+  '@vue/server-renderer@3.4.38(vue@3.4.38(typescript@5.6.2))':
     dependencies:
       '@vue/compiler-ssr': 3.4.38
       '@vue/shared': 3.4.38
-      vue: 3.4.38(typescript@5.5.4)
+      vue: 3.4.38(typescript@5.6.2)
 
   '@vue/shared@3.4.38': {}
 
-  '@vueuse/core@11.0.1(vue@3.4.38(typescript@5.5.4))':
+  '@vueuse/core@11.0.1(vue@3.4.38(typescript@5.6.2))':
     dependencies:
       '@types/web-bluetooth': 0.0.20
       '@vueuse/metadata': 11.0.1
-      '@vueuse/shared': 11.0.1(vue@3.4.38(typescript@5.5.4))
-      vue-demi: 0.14.10(vue@3.4.38(typescript@5.5.4))
+      '@vueuse/shared': 11.0.1(vue@3.4.38(typescript@5.6.2))
+      vue-demi: 0.14.10(vue@3.4.38(typescript@5.6.2))
     transitivePeerDependencies:
       - '@vue/composition-api'
       - vue
 
-  '@vueuse/integrations@11.0.1(axios@1.7.7)(focus-trap@7.5.4)(vue@3.4.38(typescript@5.5.4))':
+  '@vueuse/integrations@11.0.1(axios@1.7.7)(focus-trap@7.5.4)(vue@3.4.38(typescript@5.6.2))':
     dependencies:
-      '@vueuse/core': 11.0.1(vue@3.4.38(typescript@5.5.4))
-      '@vueuse/shared': 11.0.1(vue@3.4.38(typescript@5.5.4))
-      vue-demi: 0.14.10(vue@3.4.38(typescript@5.5.4))
+      '@vueuse/core': 11.0.1(vue@3.4.38(typescript@5.6.2))
+      '@vueuse/shared': 11.0.1(vue@3.4.38(typescript@5.6.2))
+      vue-demi: 0.14.10(vue@3.4.38(typescript@5.6.2))
     optionalDependencies:
       axios: 1.7.7
       focus-trap: 7.5.4
@@ -6197,9 +6252,9 @@ snapshots:
 
   '@vueuse/metadata@11.0.1': {}
 
-  '@vueuse/shared@11.0.1(vue@3.4.38(typescript@5.5.4))':
+  '@vueuse/shared@11.0.1(vue@3.4.38(typescript@5.6.2))':
     dependencies:
-      vue-demi: 0.14.10(vue@3.4.38(typescript@5.5.4))
+      vue-demi: 0.14.10(vue@3.4.38(typescript@5.6.2))
     transitivePeerDependencies:
       - '@vue/composition-api'
       - vue
@@ -6217,7 +6272,7 @@ snapshots:
 
   agent-base@7.1.1:
     dependencies:
-      debug: 4.3.5(supports-color@8.1.1)
+      debug: 4.3.7
     transitivePeerDependencies:
       - supports-color
 
@@ -6372,7 +6427,7 @@ snapshots:
 
   birpc@0.2.17: {}
 
-  body-parser@1.20.2:
+  body-parser@1.20.3:
     dependencies:
       bytes: 3.1.2
       content-type: 1.0.5
@@ -6382,7 +6437,7 @@ snapshots:
       http-errors: 2.0.0
       iconv-lite: 0.4.24
       on-finished: 2.4.1
-      qs: 6.11.0
+      qs: 6.13.0
       raw-body: 2.5.2
       type-is: 1.6.18
       unpipe: 1.0.0
@@ -6493,6 +6548,10 @@ snapshots:
     optionalDependencies:
       fsevents: 2.3.3
 
+  chokidar@4.0.0:
+    dependencies:
+      readdirp: 4.0.1
+
   chownr@2.0.0: {}
 
   cliui@7.0.4:
@@ -6564,14 +6623,14 @@ snapshots:
       object-assign: 4.1.1
       vary: 1.1.2
 
-  cosmiconfig@8.3.6(typescript@5.5.4):
+  cosmiconfig@8.3.6(typescript@5.6.2):
     dependencies:
       import-fresh: 3.3.0
       js-yaml: 4.1.0
       parse-json: 5.2.0
       path-type: 4.0.0
     optionalDependencies:
-      typescript: 5.5.4
+      typescript: 5.6.2
 
   cross-env@7.0.3:
     dependencies:
@@ -6630,6 +6689,10 @@ snapshots:
     optionalDependencies:
       supports-color: 8.1.1
 
+  debug@4.3.7:
+    dependencies:
+      ms: 2.1.3
+
   decamelize@4.0.0: {}
 
   decimal.js@10.4.3: {}
@@ -6691,6 +6754,8 @@ snapshots:
 
   diff@5.2.0: {}
 
+  diff@7.0.0: {}
+
   dir-glob@3.0.1:
     dependencies:
       path-type: 4.0.0
@@ -6720,10 +6785,12 @@ snapshots:
 
   encodeurl@1.0.2: {}
 
+  encodeurl@2.0.0: {}
+
   engine.io-client@6.5.4:
     dependencies:
       '@socket.io/component-emitter': 3.1.2
-      debug: 4.3.5(supports-color@8.1.1)
+      debug: 4.3.7
       engine.io-parser: 5.2.3
       ws: 8.17.1
       xmlhttprequest-ssl: 2.0.0
@@ -6738,12 +6805,12 @@ snapshots:
     dependencies:
       '@types/cookie': 0.4.1
       '@types/cors': 2.8.17
-      '@types/node': 22.5.4
+      '@types/node': 22.5.5
       accepts: 1.3.8
       base64id: 2.0.0
       cookie: 0.4.2
       cors: 2.8.5
-      debug: 4.3.5(supports-color@8.1.1)
+      debug: 4.3.7
       engine.io-parser: 5.2.3
       ws: 8.17.1
     transitivePeerDependencies:
@@ -6906,24 +6973,24 @@ snapshots:
     optionalDependencies:
       source-map: 0.6.1
 
-  eslint-compat-utils@0.5.1(eslint@9.9.1):
+  eslint-compat-utils@0.5.1(eslint@9.10.0):
     dependencies:
-      eslint: 9.9.1
+      eslint: 9.10.0
       semver: 7.6.3
 
-  eslint-config-etherpad@4.0.4(eslint@9.9.1)(typescript@5.5.4):
+  eslint-config-etherpad@4.0.4(eslint@9.10.0)(typescript@5.6.2):
     dependencies:
       '@rushstack/eslint-patch': 1.10.3
-      '@typescript-eslint/eslint-plugin': 7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.9.1)(typescript@5.5.4))(eslint@9.9.1)(typescript@5.5.4)
-      '@typescript-eslint/parser': 7.17.0(eslint@9.9.1)(typescript@5.5.4)
-      eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.17.0(eslint@9.9.1)(typescript@5.5.4))(eslint-plugin-import@2.29.1)(eslint@9.9.1)
-      eslint-plugin-cypress: 2.15.2(eslint@9.9.1)
-      eslint-plugin-eslint-comments: 3.2.0(eslint@9.9.1)
-      eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.17.0(eslint@9.9.1)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@9.9.1)
-      eslint-plugin-mocha: 10.4.3(eslint@9.9.1)
-      eslint-plugin-n: 16.6.2(eslint@9.9.1)
-      eslint-plugin-prefer-arrow: 1.2.3(eslint@9.9.1)
-      eslint-plugin-promise: 6.6.0(eslint@9.9.1)
+      '@typescript-eslint/eslint-plugin': 7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.10.0)(typescript@5.6.2))(eslint@9.10.0)(typescript@5.6.2)
+      '@typescript-eslint/parser': 7.17.0(eslint@9.10.0)(typescript@5.6.2)
+      eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.17.0(eslint@9.10.0)(typescript@5.6.2))(eslint-plugin-import@2.29.1)(eslint@9.10.0)
+      eslint-plugin-cypress: 2.15.2(eslint@9.10.0)
+      eslint-plugin-eslint-comments: 3.2.0(eslint@9.10.0)
+      eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.17.0(eslint@9.10.0)(typescript@5.6.2))(eslint-import-resolver-typescript@3.6.1)(eslint@9.10.0)
+      eslint-plugin-mocha: 10.4.3(eslint@9.10.0)
+      eslint-plugin-n: 16.6.2(eslint@9.10.0)
+      eslint-plugin-prefer-arrow: 1.2.3(eslint@9.10.0)
+      eslint-plugin-promise: 6.6.0(eslint@9.10.0)
       eslint-plugin-you-dont-need-lodash-underscore: 6.14.0
     transitivePeerDependencies:
       - eslint
@@ -6940,13 +7007,13 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
-  eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.17.0(eslint@9.9.1)(typescript@5.5.4))(eslint-plugin-import@2.29.1)(eslint@9.9.1):
+  eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.17.0(eslint@9.10.0)(typescript@5.6.2))(eslint-plugin-import@2.29.1)(eslint@9.10.0):
     dependencies:
-      debug: 4.3.5(supports-color@8.1.1)
+      debug: 4.3.7
       enhanced-resolve: 5.17.0
-      eslint: 9.9.1
-      eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.17.0(eslint@9.9.1)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.17.0(eslint@9.9.1)(typescript@5.5.4))(eslint-plugin-import@2.29.1)(eslint@9.9.1))(eslint@9.9.1)
-      eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.17.0(eslint@9.9.1)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@9.9.1)
+      eslint: 9.10.0
+      eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.17.0(eslint@9.10.0)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.17.0(eslint@9.10.0)(typescript@5.6.2))(eslint-plugin-import@2.29.1)(eslint@9.10.0))(eslint@9.10.0)
+      eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.17.0(eslint@9.10.0)(typescript@5.6.2))(eslint-import-resolver-typescript@3.6.1)(eslint@9.10.0)
       fast-glob: 3.3.2
       get-tsconfig: 4.7.6
       is-core-module: 2.15.0
@@ -6957,36 +7024,36 @@ snapshots:
       - eslint-import-resolver-webpack
       - supports-color
 
-  eslint-module-utils@2.8.1(@typescript-eslint/parser@7.17.0(eslint@9.9.1)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.17.0(eslint@9.9.1)(typescript@5.5.4))(eslint-plugin-import@2.29.1)(eslint@9.9.1))(eslint@9.9.1):
+  eslint-module-utils@2.8.1(@typescript-eslint/parser@7.17.0(eslint@9.10.0)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.17.0(eslint@9.10.0)(typescript@5.6.2))(eslint-plugin-import@2.29.1)(eslint@9.10.0))(eslint@9.10.0):
     dependencies:
       debug: 3.2.7
     optionalDependencies:
-      '@typescript-eslint/parser': 7.17.0(eslint@9.9.1)(typescript@5.5.4)
-      eslint: 9.9.1
+      '@typescript-eslint/parser': 7.17.0(eslint@9.10.0)(typescript@5.6.2)
+      eslint: 9.10.0
       eslint-import-resolver-node: 0.3.9
-      eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.17.0(eslint@9.9.1)(typescript@5.5.4))(eslint-plugin-import@2.29.1)(eslint@9.9.1)
+      eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.17.0(eslint@9.10.0)(typescript@5.6.2))(eslint-plugin-import@2.29.1)(eslint@9.10.0)
     transitivePeerDependencies:
       - supports-color
 
-  eslint-plugin-cypress@2.15.2(eslint@9.9.1):
+  eslint-plugin-cypress@2.15.2(eslint@9.10.0):
     dependencies:
-      eslint: 9.9.1
+      eslint: 9.10.0
       globals: 13.24.0
 
-  eslint-plugin-es-x@7.8.0(eslint@9.9.1):
+  eslint-plugin-es-x@7.8.0(eslint@9.10.0):
     dependencies:
-      '@eslint-community/eslint-utils': 4.4.0(eslint@9.9.1)
+      '@eslint-community/eslint-utils': 4.4.0(eslint@9.10.0)
       '@eslint-community/regexpp': 4.11.0
-      eslint: 9.9.1
-      eslint-compat-utils: 0.5.1(eslint@9.9.1)
+      eslint: 9.10.0
+      eslint-compat-utils: 0.5.1(eslint@9.10.0)
 
-  eslint-plugin-eslint-comments@3.2.0(eslint@9.9.1):
+  eslint-plugin-eslint-comments@3.2.0(eslint@9.10.0):
     dependencies:
       escape-string-regexp: 1.0.5
-      eslint: 9.9.1
+      eslint: 9.10.0
       ignore: 5.3.1
 
-  eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.17.0(eslint@9.9.1)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@9.9.1):
+  eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.17.0(eslint@9.10.0)(typescript@5.6.2))(eslint-import-resolver-typescript@3.6.1)(eslint@9.10.0):
     dependencies:
       array-includes: 3.1.8
       array.prototype.findlastindex: 1.2.5
@@ -6994,9 +7061,9 @@ snapshots:
       array.prototype.flatmap: 1.3.2
       debug: 3.2.7
       doctrine: 2.1.0
-      eslint: 9.9.1
+      eslint: 9.10.0
       eslint-import-resolver-node: 0.3.9
-      eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.17.0(eslint@9.9.1)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.17.0(eslint@9.9.1)(typescript@5.5.4))(eslint-plugin-import@2.29.1)(eslint@9.9.1))(eslint@9.9.1)
+      eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.17.0(eslint@9.10.0)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.17.0(eslint@9.10.0)(typescript@5.6.2))(eslint-plugin-import@2.29.1)(eslint@9.10.0))(eslint@9.10.0)
       hasown: 2.0.2
       is-core-module: 2.15.0
       is-glob: 4.0.3
@@ -7007,25 +7074,25 @@ snapshots:
       semver: 6.3.1
       tsconfig-paths: 3.15.0
     optionalDependencies:
-      '@typescript-eslint/parser': 7.17.0(eslint@9.9.1)(typescript@5.5.4)
+      '@typescript-eslint/parser': 7.17.0(eslint@9.10.0)(typescript@5.6.2)
     transitivePeerDependencies:
       - eslint-import-resolver-typescript
       - eslint-import-resolver-webpack
       - supports-color
 
-  eslint-plugin-mocha@10.4.3(eslint@9.9.1):
+  eslint-plugin-mocha@10.4.3(eslint@9.10.0):
     dependencies:
-      eslint: 9.9.1
-      eslint-utils: 3.0.0(eslint@9.9.1)
+      eslint: 9.10.0
+      eslint-utils: 3.0.0(eslint@9.10.0)
       globals: 13.24.0
       rambda: 7.5.0
 
-  eslint-plugin-n@16.6.2(eslint@9.9.1):
+  eslint-plugin-n@16.6.2(eslint@9.10.0):
     dependencies:
-      '@eslint-community/eslint-utils': 4.4.0(eslint@9.9.1)
+      '@eslint-community/eslint-utils': 4.4.0(eslint@9.10.0)
       builtins: 5.1.0
-      eslint: 9.9.1
-      eslint-plugin-es-x: 7.8.0(eslint@9.9.1)
+      eslint: 9.10.0
+      eslint-plugin-es-x: 7.8.0(eslint@9.10.0)
       get-tsconfig: 4.7.6
       globals: 13.24.0
       ignore: 5.3.1
@@ -7035,21 +7102,21 @@ snapshots:
       resolve: 1.22.8
       semver: 7.6.3
 
-  eslint-plugin-prefer-arrow@1.2.3(eslint@9.9.1):
+  eslint-plugin-prefer-arrow@1.2.3(eslint@9.10.0):
     dependencies:
-      eslint: 9.9.1
+      eslint: 9.10.0
 
-  eslint-plugin-promise@6.6.0(eslint@9.9.1):
+  eslint-plugin-promise@6.6.0(eslint@9.10.0):
     dependencies:
-      eslint: 9.9.1
+      eslint: 9.10.0
 
-  eslint-plugin-react-hooks@4.6.2(eslint@9.9.1):
+  eslint-plugin-react-hooks@4.6.2(eslint@9.10.0):
     dependencies:
-      eslint: 9.9.1
+      eslint: 9.10.0
 
-  eslint-plugin-react-refresh@0.4.11(eslint@9.9.1):
+  eslint-plugin-react-refresh@0.4.12(eslint@9.10.0):
     dependencies:
-      eslint: 9.9.1
+      eslint: 9.10.0
 
   eslint-plugin-you-dont-need-lodash-underscore@6.14.0:
     dependencies:
@@ -7060,9 +7127,9 @@ snapshots:
       esrecurse: 4.3.0
       estraverse: 5.3.0
 
-  eslint-utils@3.0.0(eslint@9.9.1):
+  eslint-utils@3.0.0(eslint@9.10.0):
     dependencies:
-      eslint: 9.9.1
+      eslint: 9.10.0
       eslint-visitor-keys: 2.1.0
 
   eslint-visitor-keys@2.1.0: {}
@@ -7071,13 +7138,14 @@ snapshots:
 
   eslint-visitor-keys@4.0.0: {}
 
-  eslint@9.9.1:
+  eslint@9.10.0:
     dependencies:
-      '@eslint-community/eslint-utils': 4.4.0(eslint@9.9.1)
+      '@eslint-community/eslint-utils': 4.4.0(eslint@9.10.0)
       '@eslint-community/regexpp': 4.11.0
       '@eslint/config-array': 0.18.0
       '@eslint/eslintrc': 3.1.0
-      '@eslint/js': 9.9.1
+      '@eslint/js': 9.10.0
+      '@eslint/plugin-kit': 0.1.0
       '@humanwhocodes/module-importer': 1.0.1
       '@humanwhocodes/retry': 0.3.0
       '@nodelib/fs.walk': 1.2.8
@@ -7100,7 +7168,6 @@ snapshots:
       is-glob: 4.0.3
       is-path-inside: 3.0.3
       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
@@ -7150,50 +7217,38 @@ snapshots:
       - supports-color
       - utf-8-validate
 
-  execa@8.0.1:
+  express-rate-limit@7.4.0(express@4.21.0):
     dependencies:
-      cross-spawn: 7.0.3
-      get-stream: 8.0.1
-      human-signals: 5.0.0
-      is-stream: 3.0.0
-      merge-stream: 2.0.0
-      npm-run-path: 5.3.0
-      onetime: 6.0.0
-      signal-exit: 4.1.0
-      strip-final-newline: 3.0.0
+      express: 4.21.0
 
-  express-rate-limit@7.4.0(express@4.19.2):
-    dependencies:
-      express: 4.19.2
-
-  express@4.19.2:
+  express@4.21.0:
     dependencies:
       accepts: 1.3.8
       array-flatten: 1.1.1
-      body-parser: 1.20.2
+      body-parser: 1.20.3
       content-disposition: 0.5.4
       content-type: 1.0.5
       cookie: 0.6.0
       cookie-signature: 1.0.6
       debug: 2.6.9
       depd: 2.0.0
-      encodeurl: 1.0.2
+      encodeurl: 2.0.0
       escape-html: 1.0.3
       etag: 1.8.1
-      finalhandler: 1.2.0
+      finalhandler: 1.3.1
       fresh: 0.5.2
       http-errors: 2.0.0
-      merge-descriptors: 1.0.1
+      merge-descriptors: 1.0.3
       methods: 1.1.2
       on-finished: 2.4.1
       parseurl: 1.3.3
-      path-to-regexp: 0.1.7
+      path-to-regexp: 0.1.10
       proxy-addr: 2.0.7
-      qs: 6.11.0
+      qs: 6.13.0
       range-parser: 1.2.1
       safe-buffer: 5.2.1
-      send: 0.18.0
-      serve-static: 1.15.0
+      send: 0.19.0
+      serve-static: 1.16.2
       setprototypeof: 1.2.0
       statuses: 2.0.1
       type-is: 1.6.18
@@ -7247,10 +7302,10 @@ snapshots:
     dependencies:
       to-regex-range: 5.0.1
 
-  finalhandler@1.2.0:
+  finalhandler@1.3.1:
     dependencies:
       debug: 2.6.9
-      encodeurl: 1.0.2
+      encodeurl: 2.0.0
       escape-html: 1.0.3
       on-finished: 2.4.1
       parseurl: 1.3.3
@@ -7302,7 +7357,7 @@ snapshots:
       dezalgo: 1.0.4
       hexoid: 1.0.0
       once: 1.4.0
-      qs: 6.12.3
+      qs: 6.13.0
 
   formidable@3.5.1:
     dependencies:
@@ -7373,8 +7428,6 @@ snapshots:
 
   get-stream@6.0.1: {}
 
-  get-stream@8.0.1: {}
-
   get-symbol-description@1.0.2:
     dependencies:
       call-bind: 1.0.7
@@ -7389,7 +7442,7 @@ snapshots:
     dependencies:
       basic-ftp: 5.0.5
       data-uri-to-buffer: 6.0.2
-      debug: 4.3.5(supports-color@8.1.1)
+      debug: 4.3.7
       fs-extra: 11.2.0
     transitivePeerDependencies:
       - supports-color
@@ -7505,6 +7558,14 @@ snapshots:
     dependencies:
       '@types/hast': 3.0.4
 
+  hast-util-minify-whitespace@1.0.0:
+    dependencies:
+      '@types/hast': 3.0.4
+      hast-util-embedded: 3.0.0
+      hast-util-is-element: 3.0.0
+      hast-util-whitespace: 3.0.0
+      unist-util-is: 6.0.0
+
   hast-util-parse-selector@4.0.0:
     dependencies:
       '@types/hast': 3.0.4
@@ -7604,7 +7665,7 @@ snapshots:
   http-proxy-agent@7.0.2:
     dependencies:
       agent-base: 7.1.1
-      debug: 4.3.5(supports-color@8.1.1)
+      debug: 4.3.7
     transitivePeerDependencies:
       - supports-color
 
@@ -7616,17 +7677,15 @@ snapshots:
   https-proxy-agent@7.0.5:
     dependencies:
       agent-base: 7.1.1
-      debug: 4.3.5(supports-color@8.1.1)
+      debug: 4.3.7
     transitivePeerDependencies:
       - supports-color
 
-  human-signals@5.0.0: {}
-
   i18next-browser-languagedetector@8.0.0:
     dependencies:
       '@babel/runtime': 7.24.7
 
-  i18next@23.14.0:
+  i18next@23.15.1:
     dependencies:
       '@babel/runtime': 7.24.8
 
@@ -7748,8 +7807,6 @@ snapshots:
     dependencies:
       call-bind: 1.0.7
 
-  is-stream@3.0.0: {}
-
   is-string@1.0.7:
     dependencies:
       has-tostringtag: 1.0.2
@@ -7781,7 +7838,7 @@ snapshots:
       filelist: 1.0.4
       minimatch: 3.1.2
 
-  jose@5.8.0: {}
+  jose@5.9.2: {}
 
   js-cookie@3.0.5: {}
 
@@ -7907,7 +7964,7 @@ snapshots:
       content-disposition: 0.5.4
       content-type: 1.0.5
       cookies: 0.9.1
-      debug: 4.3.5(supports-color@8.1.1)
+      debug: 4.3.7
       delegates: 1.0.0
       depd: 2.0.0
       destroy: 1.2.0
@@ -8026,13 +8083,13 @@ snapshots:
 
   lru-cache@7.18.3: {}
 
-  lucide-react@0.439.0(react@18.3.1):
+  lucide-react@0.441.0(react@18.3.1):
     dependencies:
       react: 18.3.1
 
-  magic-string@0.30.10:
+  magic-string@0.30.11:
     dependencies:
-      '@jridgewell/sourcemap-codec': 1.4.15
+      '@jridgewell/sourcemap-codec': 1.5.0
 
   mark.js@8.11.1: {}
 
@@ -8055,9 +8112,7 @@ snapshots:
 
   media-typer@0.3.0: {}
 
-  merge-descriptors@1.0.1: {}
-
-  merge-stream@2.0.0: {}
+  merge-descriptors@1.0.3: {}
 
   merge2@1.4.1: {}
 
@@ -8095,8 +8150,6 @@ snapshots:
 
   mime@2.6.0: {}
 
-  mimic-fn@4.0.0: {}
-
   mimic-response@3.1.0: {}
 
   mimic-response@4.0.0: {}
@@ -8179,13 +8232,13 @@ snapshots:
 
   netmask@2.0.2: {}
 
-  nise@6.0.0:
+  nise@6.1.1:
     dependencies:
       '@sinonjs/commons': 3.0.1
-      '@sinonjs/fake-timers': 11.2.2
-      '@sinonjs/text-encoding': 0.7.2
+      '@sinonjs/fake-timers': 13.0.2
+      '@sinonjs/text-encoding': 0.7.3
       just-extend: 6.2.0
-      path-to-regexp: 6.2.2
+      path-to-regexp: 8.1.0
 
   no-case@3.0.4:
     dependencies:
@@ -8210,10 +8263,6 @@ snapshots:
 
   normalize-url@8.0.1: {}
 
-  npm-run-path@5.3.0:
-    dependencies:
-      path-key: 4.0.0
-
   nwsapi@2.2.12: {}
 
   object-assign@4.1.1: {}
@@ -8257,7 +8306,7 @@ snapshots:
       debug: 4.3.5(supports-color@8.1.1)
       eta: 3.4.0
       got: 13.0.0
-      jose: 5.8.0
+      jose: 5.9.2
       jsesc: 3.0.2
       koa: 2.15.3
       nanoid: 5.0.7
@@ -8280,13 +8329,9 @@ snapshots:
     dependencies:
       wrappy: 1.0.2
 
-  onetime@6.0.0:
-    dependencies:
-      mimic-fn: 4.0.0
-
   only@0.0.2: {}
 
-  openapi-backend@5.10.6:
+  openapi-backend@5.11.0:
     dependencies:
       '@apidevtools/json-schema-ref-parser': 11.6.4
       ajv: 8.17.1
@@ -8339,7 +8384,7 @@ snapshots:
     dependencies:
       '@tootallnate/quickjs-emscripten': 0.23.0
       agent-base: 7.1.1
-      debug: 4.3.5(supports-color@8.1.1)
+      debug: 4.3.7
       get-uri: 6.0.3
       http-proxy-agent: 7.0.2
       https-proxy-agent: 7.0.5
@@ -8374,14 +8419,14 @@ snapshots:
 
   path-key@3.1.1: {}
 
-  path-key@4.0.0: {}
-
   path-parse@1.0.7: {}
 
-  path-to-regexp@0.1.7: {}
+  path-to-regexp@0.1.10: {}
 
   path-to-regexp@6.2.2: {}
 
+  path-to-regexp@8.1.0: {}
+
   path-type@4.0.0: {}
 
   pathe@1.1.2: {}
@@ -8394,11 +8439,11 @@ snapshots:
 
   picomatch@2.3.1: {}
 
-  playwright-core@1.47.0: {}
+  playwright-core@1.47.1: {}
 
-  playwright@1.47.0:
+  playwright@1.47.1:
     dependencies:
-      playwright-core: 1.47.0
+      playwright-core: 1.47.1
     optionalDependencies:
       fsevents: 2.3.2
 
@@ -8428,7 +8473,7 @@ snapshots:
   proxy-agent@6.4.0:
     dependencies:
       agent-base: 7.1.1
-      debug: 4.3.5(supports-color@8.1.1)
+      debug: 4.3.7
       http-proxy-agent: 7.0.2
       https-proxy-agent: 7.0.5
       lru-cache: 7.18.3
@@ -8444,11 +8489,11 @@ snapshots:
 
   punycode@2.3.1: {}
 
-  qs@6.11.0:
+  qs@6.12.3:
     dependencies:
       side-channel: 1.0.6
 
-  qs@6.12.3:
+  qs@6.13.0:
     dependencies:
       side-channel: 1.0.6
 
@@ -8489,54 +8534,54 @@ snapshots:
     dependencies:
       react: 18.3.1
 
-  react-i18next@15.0.1(i18next@23.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
+  react-i18next@15.0.2(i18next@23.15.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
     dependencies:
-      '@babel/runtime': 7.24.8
+      '@babel/runtime': 7.25.6
       html-parse-stringify: 3.0.1
-      i18next: 23.14.0
+      i18next: 23.15.1
       react: 18.3.1
     optionalDependencies:
       react-dom: 18.3.1(react@18.3.1)
 
-  react-remove-scroll-bar@2.3.6(@types/react@18.3.5)(react@18.3.1):
+  react-remove-scroll-bar@2.3.6(@types/react@18.3.8)(react@18.3.1):
     dependencies:
       react: 18.3.1
-      react-style-singleton: 2.2.1(@types/react@18.3.5)(react@18.3.1)
+      react-style-singleton: 2.2.1(@types/react@18.3.8)(react@18.3.1)
       tslib: 2.6.3
     optionalDependencies:
-      '@types/react': 18.3.5
+      '@types/react': 18.3.8
 
-  react-remove-scroll@2.5.7(@types/react@18.3.5)(react@18.3.1):
+  react-remove-scroll@2.5.7(@types/react@18.3.8)(react@18.3.1):
     dependencies:
       react: 18.3.1
-      react-remove-scroll-bar: 2.3.6(@types/react@18.3.5)(react@18.3.1)
-      react-style-singleton: 2.2.1(@types/react@18.3.5)(react@18.3.1)
+      react-remove-scroll-bar: 2.3.6(@types/react@18.3.8)(react@18.3.1)
+      react-style-singleton: 2.2.1(@types/react@18.3.8)(react@18.3.1)
       tslib: 2.6.3
-      use-callback-ref: 1.3.2(@types/react@18.3.5)(react@18.3.1)
-      use-sidecar: 1.1.2(@types/react@18.3.5)(react@18.3.1)
+      use-callback-ref: 1.3.2(@types/react@18.3.8)(react@18.3.1)
+      use-sidecar: 1.1.2(@types/react@18.3.8)(react@18.3.1)
     optionalDependencies:
-      '@types/react': 18.3.5
+      '@types/react': 18.3.8
 
-  react-router-dom@6.26.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
+  react-router-dom@6.26.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
     dependencies:
-      '@remix-run/router': 1.19.1
+      '@remix-run/router': 1.19.2
       react: 18.3.1
       react-dom: 18.3.1(react@18.3.1)
-      react-router: 6.26.1(react@18.3.1)
+      react-router: 6.26.2(react@18.3.1)
 
-  react-router@6.26.1(react@18.3.1):
+  react-router@6.26.2(react@18.3.1):
     dependencies:
-      '@remix-run/router': 1.19.1
+      '@remix-run/router': 1.19.2
       react: 18.3.1
 
-  react-style-singleton@2.2.1(@types/react@18.3.5)(react@18.3.1):
+  react-style-singleton@2.2.1(@types/react@18.3.8)(react@18.3.1):
     dependencies:
       get-nonce: 1.0.1
       invariant: 2.2.4
       react: 18.3.1
       tslib: 2.6.3
     optionalDependencies:
-      '@types/react': 18.3.5
+      '@types/react': 18.3.8
 
   react@18.3.1:
     dependencies:
@@ -8546,6 +8591,8 @@ snapshots:
     dependencies:
       picomatch: 2.3.1
 
+  readdirp@4.0.1: {}
+
   regenerator-runtime@0.14.1: {}
 
   regexp.prototype.flags@1.5.2:
@@ -8555,13 +8602,10 @@ snapshots:
       es-errors: 1.3.0
       set-function-name: 2.0.2
 
-  rehype-minify-whitespace@6.0.0:
+  rehype-minify-whitespace@6.0.1:
     dependencies:
       '@types/hast': 3.0.4
-      hast-util-embedded: 3.0.0
-      hast-util-is-element: 3.0.0
-      hast-util-whitespace: 3.0.0
-      unist-util-is: 6.0.0
+      hast-util-minify-whitespace: 1.0.0
 
   rehype-parse@9.0.0:
     dependencies:
@@ -8638,48 +8682,48 @@ snapshots:
     dependencies:
       queue-microtask: 1.2.3
 
-  rusty-store-kv-darwin-arm64@1.2.0:
+  rusty-store-kv-darwin-arm64@1.3.1:
     optional: true
 
-  rusty-store-kv-darwin-x64@1.2.0:
+  rusty-store-kv-darwin-x64@1.3.1:
     optional: true
 
-  rusty-store-kv-freebsd-x64@1.2.0:
+  rusty-store-kv-freebsd-x64@1.3.1:
     optional: true
 
-  rusty-store-kv-linux-arm-gnueabihf@1.2.0:
+  rusty-store-kv-linux-arm-gnueabihf@1.3.1:
     optional: true
 
-  rusty-store-kv-linux-arm64-gnu@1.2.0:
+  rusty-store-kv-linux-arm64-gnu@1.3.1:
     optional: true
 
-  rusty-store-kv-linux-arm64-musl@1.2.0:
+  rusty-store-kv-linux-arm64-musl@1.3.1:
     optional: true
 
-  rusty-store-kv-linux-x64-gnu@1.2.0:
+  rusty-store-kv-linux-x64-gnu@1.3.1:
     optional: true
 
-  rusty-store-kv-linux-x64-musl@1.2.0:
+  rusty-store-kv-linux-x64-musl@1.3.1:
     optional: true
 
-  rusty-store-kv-win32-arm64-msvc@1.2.0:
+  rusty-store-kv-win32-arm64-msvc@1.3.1:
     optional: true
 
-  rusty-store-kv-win32-x64-msvc@1.2.0:
+  rusty-store-kv-win32-x64-msvc@1.3.1:
     optional: true
 
-  rusty-store-kv@1.2.0:
+  rusty-store-kv@1.3.1:
     optionalDependencies:
-      rusty-store-kv-darwin-arm64: 1.2.0
-      rusty-store-kv-darwin-x64: 1.2.0
-      rusty-store-kv-freebsd-x64: 1.2.0
-      rusty-store-kv-linux-arm-gnueabihf: 1.2.0
-      rusty-store-kv-linux-arm64-gnu: 1.2.0
-      rusty-store-kv-linux-arm64-musl: 1.2.0
-      rusty-store-kv-linux-x64-gnu: 1.2.0
-      rusty-store-kv-linux-x64-musl: 1.2.0
-      rusty-store-kv-win32-arm64-msvc: 1.2.0
-      rusty-store-kv-win32-x64-msvc: 1.2.0
+      rusty-store-kv-darwin-arm64: 1.3.1
+      rusty-store-kv-darwin-x64: 1.3.1
+      rusty-store-kv-freebsd-x64: 1.3.1
+      rusty-store-kv-linux-arm-gnueabihf: 1.3.1
+      rusty-store-kv-linux-arm64-gnu: 1.3.1
+      rusty-store-kv-linux-arm64-musl: 1.3.1
+      rusty-store-kv-linux-x64-gnu: 1.3.1
+      rusty-store-kv-linux-x64-musl: 1.3.1
+      rusty-store-kv-win32-arm64-msvc: 1.3.1
+      rusty-store-kv-win32-x64-msvc: 1.3.1
 
   safe-array-concat@1.1.2:
     dependencies:
@@ -8712,7 +8756,7 @@ snapshots:
 
   semver@7.6.3: {}
 
-  send@0.18.0:
+  send@0.19.0:
     dependencies:
       debug: 2.6.9
       depd: 2.0.0
@@ -8734,12 +8778,12 @@ snapshots:
     dependencies:
       randombytes: 2.1.0
 
-  serve-static@1.15.0:
+  serve-static@1.16.2:
     dependencies:
-      encodeurl: 1.0.2
+      encodeurl: 2.0.0
       escape-html: 1.0.3
       parseurl: 1.3.3
-      send: 0.18.0
+      send: 0.19.0
     transitivePeerDependencies:
       - supports-color
 
@@ -8785,15 +8829,13 @@ snapshots:
 
   signal-exit@3.0.7: {}
 
-  signal-exit@4.1.0: {}
-
-  sinon@18.0.0:
+  sinon@19.0.2:
     dependencies:
       '@sinonjs/commons': 3.0.1
-      '@sinonjs/fake-timers': 11.2.2
-      '@sinonjs/samsam': 8.0.0
-      diff: 5.2.0
-      nise: 6.0.0
+      '@sinonjs/fake-timers': 13.0.2
+      '@sinonjs/samsam': 8.0.2
+      diff: 7.0.0
+      nise: 6.1.1
       supports-color: 7.2.0
 
   slash@3.0.0: {}
@@ -8807,7 +8849,7 @@ snapshots:
 
   socket.io-adapter@2.5.5:
     dependencies:
-      debug: 4.3.5(supports-color@8.1.1)
+      debug: 4.3.7
       ws: 8.17.1
     transitivePeerDependencies:
       - bufferutil
@@ -8828,7 +8870,7 @@ snapshots:
   socket.io-parser@4.2.4:
     dependencies:
       '@socket.io/component-emitter': 3.1.2
-      debug: 4.3.5(supports-color@8.1.1)
+      debug: 4.3.7
     transitivePeerDependencies:
       - supports-color
 
@@ -8849,7 +8891,7 @@ snapshots:
   socks-proxy-agent@8.0.4:
     dependencies:
       agent-base: 7.1.1
-      debug: 4.3.5(supports-color@8.1.1)
+      debug: 4.3.7
       socks: 2.8.3
     transitivePeerDependencies:
       - supports-color
@@ -8883,7 +8925,7 @@ snapshots:
   streamroller@3.1.5:
     dependencies:
       date-format: 4.0.14
-      debug: 4.3.5(supports-color@8.1.1)
+      debug: 4.3.7
       fs-extra: 8.1.0
     transitivePeerDependencies:
       - supports-color
@@ -8924,8 +8966,6 @@ snapshots:
 
   strip-bom@3.0.0: {}
 
-  strip-final-newline@3.0.0: {}
-
   strip-json-comments@3.1.1: {}
 
   superagent@10.1.0:
@@ -8946,13 +8986,13 @@ snapshots:
     dependencies:
       component-emitter: 1.3.1
       cookiejar: 2.1.4
-      debug: 4.3.5(supports-color@8.1.1)
+      debug: 4.3.7
       fast-safe-stringify: 2.1.1
       form-data: 4.0.0
       formidable: 2.1.2
       methods: 1.1.2
       mime: 2.6.0
-      qs: 6.12.3
+      qs: 6.13.0
       semver: 7.6.3
     transitivePeerDependencies:
       - supports-color
@@ -8961,13 +9001,13 @@ snapshots:
     dependencies:
       component-emitter: 1.3.1
       cookiejar: 2.1.4
-      debug: 4.3.5(supports-color@8.1.1)
+      debug: 4.3.7
       fast-safe-stringify: 2.1.1
       form-data: 4.0.0
       formidable: 3.5.1
       methods: 1.1.2
       mime: 2.6.0
-      qs: 6.12.3
+      qs: 6.13.0
     transitivePeerDependencies:
       - supports-color
 
@@ -9000,6 +9040,13 @@ snapshots:
 
   swagger-schema-official@2.0.0-bab6bed: {}
 
+  swagger-ui-dist@5.17.14: {}
+
+  swagger-ui-express@5.0.1(express@4.21.0):
+    dependencies:
+      express: 4.21.0
+      swagger-ui-dist: 5.17.14
+
   symbol-tree@3.2.4: {}
 
   tabbable@6.2.0: {}
@@ -9021,6 +9068,8 @@ snapshots:
 
   tinycon@0.6.8: {}
 
+  tinyexec@0.3.0: {}
+
   tinypool@1.0.0: {}
 
   tinyrainbow@1.2.0: {}
@@ -9050,9 +9099,9 @@ snapshots:
 
   trough@2.2.0: {}
 
-  ts-api-utils@1.3.0(typescript@5.5.4):
+  ts-api-utils@1.3.0(typescript@5.6.2):
     dependencies:
-      typescript: 5.5.4
+      typescript: 5.6.2
 
   tsconfig-paths@3.15.0:
     dependencies:
@@ -9065,7 +9114,7 @@ snapshots:
 
   tsscmp@1.0.6: {}
 
-  tsx@4.19.0:
+  tsx@4.19.1:
     dependencies:
       esbuild: 0.23.1
       get-tsconfig: 4.7.6
@@ -9078,6 +9127,8 @@ snapshots:
 
   type-detect@4.0.8: {}
 
+  type-detect@4.1.0: {}
+
   type-fest@0.20.2: {}
 
   type-is@1.6.18:
@@ -9117,9 +9168,9 @@ snapshots:
       is-typed-array: 1.1.13
       possible-typed-array-names: 1.0.0
 
-  typescript@5.5.4: {}
+  typescript@5.6.2: {}
 
-  ueberdb2@4.2.103: {}
+  ueberdb2@5.0.2: {}
 
   uid-safe@2.1.5:
     dependencies:
@@ -9196,20 +9247,20 @@ snapshots:
       querystringify: 2.2.0
       requires-port: 1.0.0
 
-  use-callback-ref@1.3.2(@types/react@18.3.5)(react@18.3.1):
+  use-callback-ref@1.3.2(@types/react@18.3.8)(react@18.3.1):
     dependencies:
       react: 18.3.1
       tslib: 2.6.3
     optionalDependencies:
-      '@types/react': 18.3.5
+      '@types/react': 18.3.8
 
-  use-sidecar@1.1.2(@types/react@18.3.5)(react@18.3.1):
+  use-sidecar@1.1.2(@types/react@18.3.8)(react@18.3.1):
     dependencies:
       detect-node-es: 1.1.0
       react: 18.3.1
       tslib: 2.6.3
     optionalDependencies:
-      '@types/react': 18.3.5
+      '@types/react': 18.3.8
 
   use-sync-external-store@1.2.2(react@18.3.1):
     dependencies:
@@ -9235,13 +9286,12 @@ snapshots:
       unist-util-stringify-position: 4.0.0
       vfile-message: 4.0.2
 
-  vite-node@2.0.5(@types/node@22.5.4):
+  vite-node@2.1.1(@types/node@22.5.5):
     dependencies:
       cac: 6.7.14
-      debug: 4.3.5(supports-color@8.1.1)
+      debug: 4.3.7
       pathe: 1.1.2
-      tinyrainbow: 1.2.0
-      vite: 5.4.3(@types/node@22.5.4)
+      vite: 5.4.7(@types/node@22.5.5)
     transitivePeerDependencies:
       - '@types/node'
       - less
@@ -9253,52 +9303,52 @@ snapshots:
       - supports-color
       - terser
 
-  vite-plugin-static-copy@1.0.6(vite@5.4.3(@types/node@22.5.4)):
+  vite-plugin-static-copy@1.0.6(vite@5.4.7(@types/node@22.5.5)):
     dependencies:
       chokidar: 3.6.0
       fast-glob: 3.3.2
       fs-extra: 11.2.0
       picocolors: 1.0.1
-      vite: 5.4.3(@types/node@22.5.4)
+      vite: 5.4.7(@types/node@22.5.5)
 
-  vite-plugin-svgr@4.2.0(rollup@4.21.0)(typescript@5.5.4)(vite@5.4.3(@types/node@22.5.4)):
+  vite-plugin-svgr@4.2.0(rollup@4.21.0)(typescript@5.6.2)(vite@5.4.7(@types/node@22.5.5)):
     dependencies:
       '@rollup/pluginutils': 5.1.0(rollup@4.21.0)
-      '@svgr/core': 8.1.0(typescript@5.5.4)
-      '@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(typescript@5.5.4))
-      vite: 5.4.3(@types/node@22.5.4)
+      '@svgr/core': 8.1.0(typescript@5.6.2)
+      '@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(typescript@5.6.2))
+      vite: 5.4.7(@types/node@22.5.5)
     transitivePeerDependencies:
       - rollup
       - supports-color
       - typescript
 
-  vite@5.4.3(@types/node@22.5.4):
+  vite@5.4.7(@types/node@22.5.5):
     dependencies:
       esbuild: 0.21.5
       postcss: 8.4.45
       rollup: 4.21.0
     optionalDependencies:
-      '@types/node': 22.5.4
+      '@types/node': 22.5.5
       fsevents: 2.3.3
 
-  vitepress@1.3.4(@algolia/client-search@4.23.3)(@types/node@22.5.4)(@types/react@18.3.5)(axios@1.7.7)(postcss@8.4.45)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.4):
+  vitepress@1.3.4(@algolia/client-search@4.23.3)(@types/node@22.5.5)(@types/react@18.3.8)(axios@1.7.7)(postcss@8.4.45)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.2):
     dependencies:
       '@docsearch/css': 3.6.1
-      '@docsearch/js': 3.6.1(@algolia/client-search@4.23.3)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@docsearch/js': 3.6.1(@algolia/client-search@4.23.3)(@types/react@18.3.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
       '@shikijs/core': 1.14.1
       '@shikijs/transformers': 1.14.1
       '@types/markdown-it': 14.1.2
-      '@vitejs/plugin-vue': 5.1.2(vite@5.4.3(@types/node@22.5.4))(vue@3.4.38(typescript@5.5.4))
+      '@vitejs/plugin-vue': 5.1.2(vite@5.4.7(@types/node@22.5.5))(vue@3.4.38(typescript@5.6.2))
       '@vue/devtools-api': 7.3.8
       '@vue/shared': 3.4.38
-      '@vueuse/core': 11.0.1(vue@3.4.38(typescript@5.5.4))
-      '@vueuse/integrations': 11.0.1(axios@1.7.7)(focus-trap@7.5.4)(vue@3.4.38(typescript@5.5.4))
+      '@vueuse/core': 11.0.1(vue@3.4.38(typescript@5.6.2))
+      '@vueuse/integrations': 11.0.1(axios@1.7.7)(focus-trap@7.5.4)(vue@3.4.38(typescript@5.6.2))
       focus-trap: 7.5.4
       mark.js: 8.11.1
       minisearch: 7.1.0
       shiki: 1.14.1
-      vite: 5.4.3(@types/node@22.5.4)
-      vue: 3.4.38(typescript@5.5.4)
+      vite: 5.4.7(@types/node@22.5.5)
+      vue: 3.4.38(typescript@5.6.2)
     optionalDependencies:
       postcss: 8.4.45
     transitivePeerDependencies:
@@ -9329,33 +9379,34 @@ snapshots:
       - typescript
       - universal-cookie
 
-  vitest@2.0.5(@types/node@22.5.4)(jsdom@25.0.0):
+  vitest@2.1.1(@types/node@22.5.5)(jsdom@25.0.0):
     dependencies:
-      '@ampproject/remapping': 2.3.0
-      '@vitest/expect': 2.0.5
-      '@vitest/pretty-format': 2.0.5
-      '@vitest/runner': 2.0.5
-      '@vitest/snapshot': 2.0.5
-      '@vitest/spy': 2.0.5
-      '@vitest/utils': 2.0.5
+      '@vitest/expect': 2.1.1
+      '@vitest/mocker': 2.1.1(vite@5.4.7(@types/node@22.5.5))
+      '@vitest/pretty-format': 2.1.1
+      '@vitest/runner': 2.1.1
+      '@vitest/snapshot': 2.1.1
+      '@vitest/spy': 2.1.1
+      '@vitest/utils': 2.1.1
       chai: 5.1.1
-      debug: 4.3.5(supports-color@8.1.1)
-      execa: 8.0.1
-      magic-string: 0.30.10
+      debug: 4.3.7
+      magic-string: 0.30.11
       pathe: 1.1.2
       std-env: 3.7.0
       tinybench: 2.9.0
+      tinyexec: 0.3.0
       tinypool: 1.0.0
       tinyrainbow: 1.2.0
-      vite: 5.4.3(@types/node@22.5.4)
-      vite-node: 2.0.5(@types/node@22.5.4)
+      vite: 5.4.7(@types/node@22.5.5)
+      vite-node: 2.1.1(@types/node@22.5.5)
       why-is-node-running: 2.3.0
     optionalDependencies:
-      '@types/node': 22.5.4
+      '@types/node': 22.5.5
       jsdom: 25.0.0
     transitivePeerDependencies:
       - less
       - lightningcss
+      - msw
       - sass
       - sass-embedded
       - stylus
@@ -9365,19 +9416,19 @@ snapshots:
 
   void-elements@3.1.0: {}
 
-  vue-demi@0.14.10(vue@3.4.38(typescript@5.5.4)):
+  vue-demi@0.14.10(vue@3.4.38(typescript@5.6.2)):
     dependencies:
-      vue: 3.4.38(typescript@5.5.4)
+      vue: 3.4.38(typescript@5.6.2)
 
-  vue@3.4.38(typescript@5.5.4):
+  vue@3.4.38(typescript@5.6.2):
     dependencies:
       '@vue/compiler-dom': 3.4.38
       '@vue/compiler-sfc': 3.4.38
       '@vue/runtime-dom': 3.4.38
-      '@vue/server-renderer': 3.4.38(vue@3.4.38(typescript@5.5.4))
+      '@vue/server-renderer': 3.4.38(vue@3.4.38(typescript@5.6.2))
       '@vue/shared': 3.4.38
     optionalDependencies:
-      typescript: 5.5.4
+      typescript: 5.6.2
 
   w3c-xmlserializer@5.0.0:
     dependencies:
@@ -9478,11 +9529,11 @@ snapshots:
 
   yocto-queue@0.1.0: {}
 
-  zustand@4.5.5(@types/react@18.3.5)(react@18.3.1):
+  zustand@4.5.5(@types/react@18.3.8)(react@18.3.1):
     dependencies:
       use-sync-external-store: 1.2.2(react@18.3.1)
     optionalDependencies:
-      '@types/react': 18.3.5
+      '@types/react': 18.3.8
       react: 18.3.1
 
   zwitch@2.0.4: {}
diff --git a/settings.json.docker b/settings.json.docker
index bbe96fc5194617d375ac5f36303c61feac196900..da1d51c13a8ad8bbdb4e70e8679af4fdf7b4a44c 100644
--- a/settings.json.docker
+++ b/settings.json.docker
@@ -171,6 +171,14 @@
    */
   "showSettingsInAdminPage": "${SHOW_SETTINGS_IN_ADMIN_PAGE:true}",
 
+  /*
+   * Settings for cleanup of pads
+   */
+  "cleanup": {
+    "enabled": false,
+    "keepRevisions": 5
+  },
+
   /*
     The authentication method used by the server.
     The default value is sso
@@ -194,6 +202,15 @@
           },
   */
 
+
+  /*
+   * Enables the use of a different server. We have a different one that syncs changes from the original server.
+   * It is hosted on GitHub and should not be blocked by many firewalls.
+   *  https://etherpad.org/ep_infos
+   */
+
+  "updateServer": "https://etherpad.org/ep_infos",
+
   /*
    * The type of the database.
    *
diff --git a/settings.json.template b/settings.json.template
index 66c9a7df2fdb44be259d6e6a6cb8c9d0075e1068..2d856f42e4c09bd3b8d12c022e04e8a07eed1e2b 100644
--- a/settings.json.template
+++ b/settings.json.template
@@ -162,6 +162,14 @@
    */
   "showSettingsInAdminPage": true,
 
+  /*
+   * Settings for cleanup of pads
+   */
+  "cleanup": {
+    "enabled": false,
+    "keepRevisions": 5
+  },
+
   /*
    * Node native SSL support
    *
@@ -271,6 +279,14 @@
     "pageDown":  true
   },
 
+  /*
+   * Enables the use of a different server. We have a different one that syncs changes from the original server.
+   * It is hosted on GitHub and should not be blocked by many firewalls.
+   *  https://etherpad.org/ep_infos
+   */
+
+  "updateServer": "https://etherpad.org/ep_infos",
+
   /*
    * Should we suppress errors from being visible in the default Pad Text?
    */
diff --git a/src/ep.json b/src/ep.json
index c9b26c175aa38e5ad12d003f8ee6adbb4cee45ab..83dfc509db85a53bb89fdf6d10fca6198d59ff4a 100644
--- a/src/ep.json
+++ b/src/ep.json
@@ -82,6 +82,12 @@
         "expressCreateServer": "ep_etherpad-lite/node/hooks/express/errorhandling"
       }
     },
+    {
+      "name": "restApi",
+      "hooks": {
+        "expressCreateServer": "ep_etherpad-lite/node/handler/RestAPI"
+      }
+    },
     {
       "name": "socketio",
       "hooks": {
diff --git a/src/node/handler/APIHandler.ts b/src/node/handler/APIHandler.ts
index 5feb74eb9654f94ba4cc038a19b4cd63cef51f82..b108f50f0a098ba2a6a7a911b72386e9942e74e2 100644
--- a/src/node/handler/APIHandler.ts
+++ b/src/node/handler/APIHandler.ts
@@ -24,10 +24,10 @@ import {MapArrayType} from "../types/MapType";
 const api = require('../db/API');
 const padManager = require('../db/PadManager');
 import createHTTPError from 'http-errors';
-import {Http2ServerRequest, Http2ServerResponse} from "node:http2";
+import {Http2ServerRequest} from "node:http2";
 import {publicKeyExported} from "../security/OAuth2Provider";
 import {jwtVerify} from "jose";
-import {apikey} from './APIKeyHandler'
+import {APIFields, apikey} from './APIKeyHandler'
 // a list of all functions
 const version:MapArrayType<any> = {};
 
@@ -141,6 +141,7 @@ version['1.3.0'] = {
   setText: ['padID', 'text', 'authorId'],
 };
 
+
 // set the latest available API version here
 exports.latestApiVersion = '1.3.0';
 
@@ -148,13 +149,6 @@ exports.latestApiVersion = '1.3.0';
 exports.version = version;
 
 
-type APIFields = {
-  apikey: string;
-  api_key: string;
-  padID: string;
-  padName: string;
-  authorization: string;
-}
 
 /**
  * Handles an HTTP API call
diff --git a/src/node/handler/APIKeyHandler.ts b/src/node/handler/APIKeyHandler.ts
index b4e70f6e4b0e04866ad7c51d59bcbae72baf34be..5a00453b14df82d90a662a11de27a057e8b3b403 100644
--- a/src/node/handler/APIKeyHandler.ts
+++ b/src/node/handler/APIKeyHandler.ts
@@ -7,6 +7,16 @@ const settings = require('../utils/Settings');
 
 const apiHandlerLogger = log4js.getLogger('APIHandler');
 
+
+
+export type APIFields = {
+  apikey: string;
+  api_key: string;
+  padID: string;
+  padName: string;
+  authorization: string;
+}
+
 // ensure we have an apikey
 export let apikey:string|null = null;
 const apikeyFilename = absolutePaths.makeAbsolute(argv.apikey || './APIKEY.txt');
diff --git a/src/node/handler/PadMessageHandler.ts b/src/node/handler/PadMessageHandler.ts
index 6d2ee0d5785f4adb9d8eb901ceebd767e1dc10b8..9f1c9e86bc0b93f838d9a8a9719e0de1fe2bead0 100644
--- a/src/node/handler/PadMessageHandler.ts
+++ b/src/node/handler/PadMessageHandler.ts
@@ -1147,13 +1147,13 @@ const getChangesetInfo = async (pad: PadType, startNum: number, endNum:number, g
     getPadLines(pad, startNum - 1),
     // Get all needed composite Changesets.
     ...compositesChangesetNeeded.map(async (item) => {
-      const changeset = await composePadChangesets(pad, item.start, item.end);
+      const changeset = await exports.composePadChangesets(pad, item.start, item.end);
       composedChangesets[`${item.start}/${item.end}`] = changeset;
     }),
     // Get all needed revision Dates.
     ...revTimesNeeded.map(async (revNum) => {
       const revDate = await pad.getRevisionDate(revNum);
-      revisionDate[revNum] = Math.floor(revDate / 1000);
+      revisionDate[revNum] = revDate;
     }),
   ]);
 
@@ -1213,7 +1213,7 @@ const getPadLines = async (pad: PadType, revNum: number) => {
  * Tries to rebuild the composePadChangeset function of the original Etherpad
  * https://github.com/ether/pad/blob/master/etherpad/src/etherpad/control/pad/pad_changeset_control.js#L241
  */
-const composePadChangesets = async (pad: PadType, startNum: number, endNum: number) => {
+exports.composePadChangesets = async (pad: PadType, startNum: number, endNum: number) => {
   // fetch all changesets we need
   const headNum = pad.getHeadRevisionNumber();
   endNum = Math.min(endNum, headNum + 1);
diff --git a/src/node/handler/RestAPI.ts b/src/node/handler/RestAPI.ts
new file mode 100644
index 0000000000000000000000000000000000000000..7b1b82309d766a0e7c8fe1de870e3d88025890bb
--- /dev/null
+++ b/src/node/handler/RestAPI.ts
@@ -0,0 +1,1527 @@
+import {ArgsExpressType} from "../types/ArgsExpressType";
+import {MapArrayType} from "../types/MapType";
+import {IncomingForm} from "formidable";
+import {ErrorCaused} from "../types/ErrorCaused";
+import createHTTPError from "http-errors";
+
+const apiHandler = require('./APIHandler')
+import {serve, setup} from 'swagger-ui-express'
+import express from "express";
+
+const settings = require('../utils/Settings')
+
+
+type RestAPIMapping = {
+  apiVersion: string;
+  functionName: string,
+  summary?: string,
+  operationId?: string,
+  requestBody?: any,
+  responses?: any,
+  tags?: string[],
+}
+
+
+const mapping = new Map<string, Record<string, RestAPIMapping>>
+
+
+const GET = "GET"
+const POST = "POST"
+const PUT = "PUT"
+const DELETE = "DELETE"
+const PATCH = "PATCH"
+
+
+const defaultResponses = {
+  "200": {
+    "description": "ok (code 0)",
+    "content": {
+      "application/json": {
+        "schema": {
+          "type": "object",
+          "properties": {
+            "code": {
+              "type": "integer",
+              "example": 0
+            },
+            "message": {
+              "type": "string",
+              "example": "ok"
+            },
+            "data": {
+              "type": "object",
+              "properties": {
+                "groupID": {
+                  "type": "string"
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  },
+  "400": {
+    "description": "generic api error (code 1)",
+    "content": {
+      "application/json": {
+        "schema": {
+          "type": "object",
+          "properties": {
+            "code": {
+              "type": "integer",
+              "example": 1
+            },
+            "message": {
+              "type": "string",
+              "example": "error message"
+            },
+            "data": {
+              "type": "object",
+              "example": null
+            }
+          }
+        }
+      }
+    }
+  },
+  "401": {
+    "description": "no or wrong API key (code 4)",
+    "content": {
+      "application/json": {
+        "schema": {
+          "type": "object",
+          "properties": {
+            "code": {
+              "type": "integer",
+              "example": 4
+            },
+            "message": {
+              "type": "string",
+              "example": "no or wrong API key"
+            },
+            "data": {
+              "type": "object",
+              "example": null
+            }
+          }
+        }
+      }
+    }
+  },
+  "500": {
+    "description": "internal api error (code 2)",
+    "content": {
+      "application/json": {
+        "schema": {
+          "type": "object",
+          "properties": {
+            "code": {
+              "type": "integer",
+              "example": 2
+            },
+            "message": {
+              "type": "string",
+              "example": "internal error"
+            },
+            "data": {
+              "type": "object",
+              "example": null
+            }
+          }
+        }
+      }
+    }
+  },
+  "tags": [
+    "group"
+  ],
+  "parameters": []
+}
+
+const prepareResponses = (data: {
+  type: string,
+  properties: Record<string, {
+    type: string,
+    items?: Record<string, any>,
+    properties?: Record<string, any>,
+  }>,
+}) => {
+  return {
+    ...defaultResponses,
+    200: {
+      ...defaultResponses["200"],
+      content: {
+        ...defaultResponses["200"].content,
+        "application/json": {
+          schema: {
+            type: "object",
+            properties: {
+              ...defaultResponses["200"].content["application/json"].schema.properties,
+              data: data
+            }
+          }
+        }
+      }
+    }
+  }
+}
+
+
+const prepareDefinition = (mapping: Map<string, Record<string, RestAPIMapping>>, address: string) => {
+  const authenticationMethod = settings.authenticationMethod
+
+
+  const definitions: {
+    "openapi": string,
+    "info": {
+      "title": string,
+      "description": string,
+      "termsOfService": string,
+      "contact": {
+        "name": string,
+        "url": string,
+        "email": string,
+      },
+    },
+    "components": {
+      "securitySchemes": {
+        "apiKey": {
+          "type": string,
+          "name": string,
+          "in": string
+
+        },
+        "sso"?: {
+          "type": string,
+          "flows": {
+            "authorizationCode": {
+              "authorizationUrl": string,
+              "tokenUrl": string,
+              "scopes": {
+                "openid": string,
+                "profile": string,
+                "email": string,
+                "admin": string
+              }
+            }
+          }
+        }
+      },
+    },
+    "servers": [
+      {
+        "url": string
+      }
+    ],
+    "paths": Record<string, Record<string, {
+      "summary": string,
+      "operationId": string,
+      "requestBody"?: any,
+      "responses": any,
+      "parameters"?: any,
+      "tags": Array<string | { name: string, description: string }>,
+    }>>,
+    "security": any[]
+  } = {
+    "openapi": "3.0.2",
+    "info": {
+      "title": "Etherpad API",
+      "description": "Etherpad is a real-time collaborative editor scalable to thousands of simultaneous real time users. It provides full data export capabilities, and runs on your server, under your control.",
+      "termsOfService": "https://etherpad.org/",
+      "contact": {
+        "name": "The Etherpad Foundation",
+        "url": "https://etherpad.org/",
+        "email": "",
+      },
+    },
+    "components": {
+      "securitySchemes": {
+        "apiKey": {
+          "type": "apiKey",
+          "name": "apikey",
+          "in": "query"
+
+        },
+      },
+    },
+    "servers": [
+      {
+        "url": `${address}/api/2`
+      }
+    ],
+    "paths": {},
+    "security": []
+  }
+
+  if (authenticationMethod === "apikey") {
+    definitions.security = [
+      {
+        "apiKey": []
+      }
+    ]
+  } else if (authenticationMethod === "sso") {
+    definitions.components.securitySchemes.sso = {
+      type: "oauth2",
+      flows: {
+        authorizationCode: {
+          authorizationUrl: settings.sso.issuer + "/oidc/auth",
+          tokenUrl: settings.sso.issuer + "/oidc/token",
+          scopes: {
+            openid: "openid",
+            profile: "profile",
+            email: "email",
+            admin: "admin"
+          }
+        }
+      },
+    }
+
+    definitions.security = [
+      {
+        "sso": []
+      }
+    ]
+  }
+
+
+  for (const [method, value] of mapping) {
+    for (const [path, mapping] of Object.entries(value)) {
+      const {apiVersion, functionName, summary, operationId, requestBody, responses, tags} = mapping
+      if (!definitions.paths[path]) {
+        definitions.paths[path] = {}
+      }
+
+      const methodLowercased = method.toLowerCase()
+
+      definitions.paths[path][methodLowercased] = {
+        summary: summary!,
+        operationId: operationId!,
+        responses,
+        tags: tags!
+      }
+
+      if (method === GET) {
+        definitions.paths[path][methodLowercased].parameters = requestBody
+      } else {
+        definitions.paths[path][methodLowercased].requestBody = requestBody
+      }
+    }
+  }
+  return definitions
+}
+
+
+export const expressCreateServer = async (hookName: string, {app}: ArgsExpressType) => {
+  mapping.set(GET, {})
+  mapping.set(POST, {})
+  mapping.set(PUT, {})
+  mapping.set(DELETE, {})
+  mapping.set(PATCH, {})
+
+  // Version 1
+  mapping.get(POST)!["/groups"] = {
+    apiVersion: '1',
+    functionName: 'createGroup', summary: 'Creates a new group',
+    operationId: 'createGroup', tags: ['group'], responses: prepareResponses({type: "object", properties: {groupID: {type: "string"}}})
+
+  }
+  mapping.get(POST)!["/groups/createIfNotExistsFor"] = {
+    apiVersion: '1', functionName: 'createGroupIfNotExistsFor',
+    requestBody: {
+      content: {
+        "application/json": {
+          schema: {
+            type: "object",
+            properties: {
+              groupMapper: {
+                type: "string"
+              }
+            },
+            required: ["groupMapper"]
+          }
+        }
+      }
+    },
+    responses: prepareResponses({type: "object", properties: {groupID: {type: "string"}}}),
+    summary: "Creates a new group if it doesn't exist", operationId: 'createGroupIfNotExistsFor', tags: ['group']
+  };
+  mapping.get(GET)!["/groups/pads"] = {
+    apiVersion: '1', functionName: 'listPads',
+    summary: "Lists all pads in a group", tags: ['group'],
+    operationId: 'listPads', responses: prepareResponses({type: "object", properties: {padIDs: {type: "string"}}}),
+    requestBody: [
+      {
+        "name": "groupID",
+        "in": "query",
+        "schema": {
+          "type": "string"
+        }
+      }
+    ]
+  }
+  mapping.get(DELETE)!["/groups"] = {
+    apiVersion: '1', functionName: 'deleteGroup', responses: prepareResponses({type: "object", properties: {}}), requestBody: {
+      content: {
+        "application/json": {
+          schema: {
+            type: "object",
+            properties: {
+              groupID: {
+                type: "string"
+              }
+            },
+            required: ["groupID"]
+          }
+        }
+      }
+    }, summary: "Deletes a group", operationId: 'deleteGroup', tags: ['group']
+  }
+
+  mapping.get(POST)!["/authors"] = {
+    apiVersion: '1', functionName: 'createAuthor', requestBody: {
+      content: {
+        "application/json": {
+          schema: {
+            type: "object",
+            properties: {
+              name: {
+                type: "string"
+              }
+            },
+            required: ["name"]
+          }
+        }
+      }
+    }, tags: ["author"]
+  }
+
+
+  mapping.get(POST)!["/authors/createIfNotExistsFor"] = {
+    apiVersion: '1', functionName: 'createAuthorIfNotExistsFor',
+    responses: prepareResponses({type: "object", properties: {authorID: {type: "string"}}}),
+    requestBody: {
+      content: {
+        "application/json": {
+          schema: {
+            type: "object",
+            properties: {
+              authorMapper: {
+                type: "string"
+              },
+              name: {
+                type: "string"
+              }
+            },
+            required: ["authorMapper", "name"]
+          }
+        }
+      }
+    },
+    tags: ["author"],
+  }
+
+
+  mapping.get(GET)!["/authors/pads"] = {
+    apiVersion: '1', functionName: 'listPadsOfAuthor',
+    requestBody: [
+      {
+        "name": "authorID",
+        "in": "query",
+        "schema": {
+          "type": "string"
+        }
+      }
+    ],
+    responses: prepareResponses({type: "object", properties: {padIDs: {type: "array", items: {type: "string"}}}}),
+    tags: ["author"]
+  }
+  mapping.get(POST)!["/sessions"] = {
+    apiVersion: '1', functionName: 'createSession',
+    requestBody: {
+      content: {
+        "application/json": {
+          schema: {
+            type: "object",
+            properties: {
+              groupID: {
+                type: "string"
+              },
+              authorID: {
+                type: "string"
+              },
+              validUntil: {
+                type: "string"
+              }
+            },
+            required: ["groupID", "authorID", "validUntil"]
+          }
+        }
+      }
+    },
+    responses: prepareResponses({type: "object", properties: {sessionID: {type: "string"}}}),
+    tags: ['session']
+  }
+
+  mapping.get(DELETE)!["/sessions"] = {
+    apiVersion: '1', functionName: 'deleteSession',
+    requestBody: {
+      content: {
+        "application/json": {
+          schema: {
+            type: "object",
+            properties: {
+              sessionID: {
+                type: "string"
+              }
+            },
+            required: ["sessionID"]
+          }
+        }
+      }
+    },
+    responses: prepareResponses({type: "object", properties: {}}),
+    tags: ['session']
+  }
+
+
+  mapping.get(GET)!["/sessions/info"] = {
+    apiVersion: '1', functionName: 'getSessionInfo',
+    requestBody: [
+      {
+        "name": "sessionID",
+        "in": "query",
+        "schema": {
+          "type": "string"
+        }
+      }
+    ],
+    responses: prepareResponses({
+      "type": "object",
+      "properties": {
+        id: {
+          type: "string"
+        },
+        "groupID": {
+          "type": "string"
+        },
+        "authorID": {
+          "type": "string"
+        },
+        "validUntil": {
+          "type": "string"
+        }
+      }
+    }),
+    tags: ['session']
+  }
+
+
+  mapping.get(GET)!["/sessions/group"] = {
+    apiVersion: '1', functionName: 'listSessionsOfGroup', summary: 'Lists all sessions in a group',
+    operationId: 'listSessionsOfGroup', tags: ['session'],
+    responses: prepareResponses({
+      type: "object", "properties": {
+        "sessions": {
+          "type": "array",
+          "items": {
+            "type": "object",
+            "properties": {
+              "id": {
+                "type": "string"
+              },
+              "authorID": {
+                "type": "string"
+              },
+              "groupID": {
+                "type": "string"
+              },
+              "validUntil": {
+                "type": "integer"
+              }
+            }
+          }
+        }
+      }
+    }),
+    requestBody: [
+      {
+        "name": "groupID",
+        "in": "query",
+        "schema": {
+          "type": "string"
+        }
+      }
+    ]
+  }
+  mapping.get(GET)!["/sessions/author"] = {
+    apiVersion: '1', functionName: 'listSessionsOfAuthor',
+    summary: 'Lists all sessions of an author', operationId: 'listSessionsOfAuthor', tags: ['session'],
+    responses: prepareResponses({
+      type: "object", "properties": {
+        "sessions": {
+          "type": "array",
+          "items": {
+            "type": "object",
+            "properties": {
+              "id": {
+                "type": "string"
+              },
+              "authorID": {
+                "type": "string"
+              },
+              "groupID": {
+                "type": "string"
+              },
+              "validUntil": {
+                "type": "integer"
+              }
+            }
+          }
+        }
+      }
+    }),
+    requestBody: [
+      {
+        "name": "authorID",
+        "in": "query",
+        "schema": {
+          "type": "string"
+        }
+      }
+    ]
+  }
+
+
+  mapping.get(GET)!["/pads/text"] = {
+    apiVersion: '1', functionName: 'getText',
+    responses: prepareResponses({type: "object", properties: {text: {type: "string"}}}),
+    requestBody: [
+      {
+        "name": "padID",
+        "in": "query",
+        "schema": {
+          "type": "string"
+        }
+      }
+    ],
+    tags: ['pad']
+  }
+
+
+  mapping.get(GET)!["/pads/html"] = {
+    apiVersion: '1', functionName: 'getHTML',
+    responses: prepareResponses({type: "object", properties: {html: {type: "string"}}}),
+    requestBody: [
+      {
+        "name": "padID",
+        "in": "query",
+        "schema": {
+          "type": "string"
+        }
+      }
+    ],
+    summary: 'Get the HTML of a pad',
+    tags: ['pad']
+  }
+  mapping.get(GET)!["/pads/revisions"] = {
+    apiVersion: '1', functionName: 'getRevisionsCount',
+    responses: prepareResponses({type: "object", properties: {revisions: {type: "integer"}}}),
+    requestBody: [
+      {
+        "name": "padID",
+        "in": "query",
+        "schema": {
+          "type": "string"
+        }
+      }
+    ],
+    summary: 'Get the number of revisions of a pad',
+    tags: ['pad']
+  }
+
+  mapping.get(GET)!["/pads/lastEdited"] = {
+    apiVersion: '1', functionName: 'getLastEdited',
+    responses: prepareResponses({type: "object", properties: {lastEdited: {type: "integer"}}}),
+    requestBody: [
+      {
+        "name": "padID",
+        "in": "query",
+        "schema": {
+          "type": "string"
+        }
+      }
+    ],
+    summary: 'Get the timestamp of the last revision of a pad',
+    tags: ['pad']
+  }
+
+
+  mapping.get(DELETE)!["/pads"] = {
+    apiVersion: '1', functionName: 'deletePad',
+    responses: prepareResponses({type: "object", properties: {}}),
+    requestBody: {
+      content: {
+        "application/json": {
+          schema: {
+            type: "object",
+            properties: {
+              padID: {
+                type: "string"
+              }
+            },
+            required: ["padID"]
+          }
+        }
+      }
+    },
+    summary: 'Deletes a pad',
+    tags: ['pad']
+  }
+  mapping.get(GET)!["/pads/readonly"] = {
+    apiVersion: '1', functionName: 'getReadOnlyID',
+    responses: prepareResponses({type: "object", properties: {readOnlyID: {type: "string"}}}),
+    requestBody: [
+      {
+        "name": "padID",
+        "in": "query",
+        "schema": {
+          "type": "string"
+        }
+      }
+    ],
+    summary: 'Get the read only id of a pad',
+    tags: ['pad']
+  }
+
+  mapping.get(POST)!["/pads/publicStatus"] = {
+    apiVersion: '1', functionName: 'setPublicStatus',
+    responses: prepareResponses({type: "object", properties: {}}),
+    requestBody: {
+      content: {
+        "application/json": {
+          schema: {
+            type: "object",
+            properties: {
+              padID: {
+                type: "string"
+              },
+              publicStatus: {
+                type: "boolean"
+              }
+            },
+            required: ["padID", "publicStatus"]
+          }
+        }
+      }
+    },
+    summary: 'Set the public status of a pad',
+    tags: ['pad']
+
+  }
+  mapping.get(GET)!["/pads/publicStatus"] = {
+    apiVersion: '1', functionName: 'getPublicStatus',
+    responses: prepareResponses({type: "object", properties: {publicStatus: {type: "boolean"}}}),
+    requestBody: [
+      {
+        "name": "padID",
+        "in": "query",
+        "schema": {
+          "type": "string"
+        }
+      }
+    ],
+    summary: 'Get the public status of a pad',
+    tags: ['pad']
+  }
+  mapping.get(GET)!["/pads/authors"] = {
+    apiVersion: '1', functionName: 'listAuthorsOfPad',
+    responses: prepareResponses({type: "object", properties: {authorIDs: {type: "array", items: {type: "string"}}}}),
+    requestBody: [
+      {
+        "name": "padID",
+        "in": "query",
+        "schema": {
+          "type": "string"
+        }
+      }
+    ],
+    summary: 'Get the authors of a pad',
+    tags: ['pad']
+  }
+  mapping.get(GET)!["/pads/usersCount"] = {
+    apiVersion: '1', functionName: 'padUsersCount',
+    responses: prepareResponses({type: "object", properties: {padUsersCount: {type: "integer"}}}),
+    requestBody: [
+      {
+        "name": "padID",
+        "in": "query",
+        "schema": {
+          "type": "string"
+        }
+      }
+    ],
+    summary: 'Get the number of users currently editing a pad',
+    tags: ['pad']
+  }
+
+
+  // Version 1.1
+  mapping.get(GET)!["/authors/name"] = {
+    apiVersion: '1.1', functionName: 'getAuthorName',
+    responses: prepareResponses({type: "object", properties: {authorName: {type: "string"}}}),
+    requestBody: [
+      {
+        "name": "authorID",
+        "in": "query",
+        "schema": {
+          "type": "string"
+        }
+      }
+    ],
+    summary: 'Get the name of an author',
+    tags: ['author']
+  }
+  mapping.get(GET)!["/pads/users"] = {
+    apiVersion: '1.1', functionName: 'padUsers',
+    responses: prepareResponses({
+      type: "object", properties: {
+        padUsers: {
+          type: "array", "items": {
+            "type": "object",
+            "properties": {
+              "id": {
+                "type": "string"
+              },
+              "colorId": {
+                "type": "string"
+              },
+              "name": {
+                "type": "string"
+              },
+              "timestamp": {
+                "type": "integer"
+              }
+            }
+          }
+        }
+      }
+    }),
+    requestBody: [
+      {
+        "name": "padID",
+        "in": "query",
+        "schema": {
+          "type": "string"
+        }
+      }
+    ],
+    summary: 'Get the users currently editing a pad',
+    tags: ['pad']
+  }
+
+
+  mapping.get(POST)!["/pads/clientsMessage"] = {
+    apiVersion: '1.1', functionName: 'sendClientsMessage',
+    responses: prepareResponses({type: "object", properties: {}}),
+    requestBody: {
+      content: {
+        "application/json": {
+          schema: {
+            type: "object",
+            properties: {
+              padID: {
+                type: "string"
+              },
+              msg: {
+                type: "string"
+              }
+            },
+            required: ["padID", "msg"]
+          }
+        }
+      }
+    },
+    summary: 'Send a message to all clients of a pad',
+    tags: ['pad']
+  }
+
+
+  mapping.get(GET)!["/groups"] = {
+    apiVersion: '1.1', functionName: 'listAllGroups',
+    responses: prepareResponses({type: "object", properties: {groupIDs: {type: "array", items: {type: "string"}}}}),
+    summary: 'Lists all groups',
+    tags: ['group']
+  }
+
+
+  // Version 1.2
+  mapping.get(GET)!["/checkToken"] = {
+    apiVersion: '1.2', functionName: 'checkToken',
+    responses: prepareResponses({type: "object", properties: {}}),
+    requestBody: [
+      {
+        "name": "token",
+        "in": "query",
+        "schema": {
+          "type": "string"
+        }
+      }
+    ],
+    summary: 'Check if a token is valid',
+    tags: ['token']
+
+  }
+
+  // Version 1.2.1
+  mapping.get(GET)!["/pads"] = {
+    apiVersion: '1.2.1', functionName: 'listAllPads',
+    summary: 'Lists all pads',
+    tags: ['pad'],
+    requestBody: [
+      {
+        "name": "groupID",
+        "in": "query",
+        "schema": {
+          "type": "string"
+        }
+      }
+    ],
+    responses: prepareResponses({type: "object", properties: {padIDs: {type: "array", items: {type: "string"}}}})
+  }
+
+  // Version 1.2.7
+  mapping.get(POST)!["/pads/diff"] = {
+    apiVersion: '1.2.7', functionName: 'createDiffHTML',
+    requestBody: {
+      content: {
+        "application/json": {
+          schema: {
+            type: "object",
+            properties: {
+              padID: {
+                type: "string"
+              },
+              startRev: {
+                type: "integer"
+              },
+              endRev: {
+                type: "integer"
+              }
+            },
+            required: ["padID", "startRev", "endRev"]
+          }
+        }
+      }
+    },
+    responses: prepareResponses({type: "object", properties: {}}),
+    summary: 'Creates a diff of a pad',
+    tags: ['pad']
+  }
+  mapping.get(GET)!["/pads/chatHistory"] = {
+    apiVersion: '1.2.7', functionName: 'getChatHistory',
+    responses: prepareResponses({
+      type: "object", properties: {
+        messages: {
+          type: "array", items: {
+            type: "object", properties: {
+              "text": {
+                "type": "string"
+              },
+              "userId": {
+                "type": "string"
+              },
+              "userName": {
+                "type": "string"
+              },
+              "time": {
+                "type": "integer"
+              }
+            }
+          }
+        }
+      }
+    }),
+    requestBody: [
+      {
+        "name": "padID",
+        "in": "query",
+        "schema": {
+          "type": "string"
+        }
+      }
+    ],
+    summary: 'Get the chat history of a pad',
+    tags: ['pad']
+  }
+  mapping.get(GET)!["/pads/chatHead"] = {
+    apiVersion: '1.2.7', functionName: 'getChatHead',
+    responses: prepareResponses({
+      type: "object", properties: {
+        chatHead: {
+          type: "object",
+          properties: {
+            "text": {
+              "type": "string"
+            },
+            "userId": {
+              "type": "string"
+            },
+            "userName": {
+              "type": "string"
+            },
+            "time": {
+              "type": "integer"
+            }
+          }
+        }
+
+      }
+    }),
+    requestBody: [
+      {
+        "name": "padID",
+        "in": "query",
+        "schema": {
+          "type": "string"
+        }
+      }
+    ],
+    summary: 'Get the chat head of a pad',
+    tags: ['pad']
+
+  }
+
+  // Version 1.2.8
+  mapping.get(GET)!["/pads/attributePool"] = {
+    apiVersion: '1.2.8', functionName: 'getAttributePool',
+    responses: prepareResponses({type: "object", properties: {}}),
+    requestBody: [
+      {
+        "name": "padID",
+        "in": "query",
+        "schema": {
+          "type": "string"
+        }
+      }
+    ],
+    summary: 'Get the attribute pool of a pad',
+    tags: ['pad']
+  }
+  mapping.get(GET)!["/pads/revisionChangeset"] = {
+    apiVersion: '1.2.8', functionName: 'getRevisionChangeset',
+    responses: prepareResponses({type: "object", properties: {}}),
+    requestBody: [
+      {
+        "name": "padID",
+        "in": "query",
+        "schema": {
+          "type": "string"
+        }
+      },
+      {
+        "name": "rev",
+        "in": "query",
+        "schema": {
+          "type": "integer"
+        }
+      }
+    ],
+    summary: 'Get the changeset of a revision of a pad',
+    tags: ['pad']
+  }
+
+  // Version 1.2.9
+  mapping.get(POST)!["/pads/copypad"] = {
+    apiVersion: '1.2.9', functionName: 'copyPad',
+    requestBody: {
+      content: {
+        "application/json": {
+          schema: {
+            type: "object",
+            properties: {
+              sourceID: {
+                type: "string"
+              },
+              destinationID: {
+                type: "string"
+              },
+              force: {
+                type: "boolean"
+              }
+            },
+            required: ["sourceID", "destinationID"]
+          }
+        }
+      }
+    },
+    responses: prepareResponses({type: "object", properties: {}}),
+    summary: 'Copies a pad',
+    tags: ['pad']
+  }
+
+
+  mapping.get(POST)!["/pads/movePad"] = {
+    apiVersion: '1.2.9', functionName: 'movePad',
+    requestBody: {
+      content: {
+        "application/json": {
+          schema: {
+            type: "object",
+            properties: {
+              sourceID: {
+                type: "string"
+              },
+              destinationID: {
+                type: "string"
+              },
+              force: {
+                type: "boolean"
+              }
+            },
+            required: ["sourceID", "destinationID"]
+          }
+        }
+      }
+    },
+    responses: prepareResponses({type: "object", properties: {}}),
+    summary: 'Moves a pad',
+    tags: ['pad']
+  }
+
+  // Version 1.2.10
+  mapping.get(POST)!["/pads/padId"] = {
+    apiVersion: '1.2.10', functionName: 'getPadID',
+    requestBody: {
+      content: {
+        "application/json": {
+          schema: {
+            type: "object",
+            properties: {
+              roID: {
+                type: "string"
+              }
+            },
+            required: ["roID"]
+          }
+        }
+      }
+    },
+    responses: prepareResponses({type: "object", properties: {}}),
+    summary: 'Get the pad id of a pad',
+    tags: ['pad']
+  }
+
+  // Version 1.2.11
+  mapping.get(GET)!["/savedRevisions"] = {
+    apiVersion: '1.2.11', functionName: 'listSavedRevisions',
+    responses: prepareResponses({type: "object", properties: {savedRevisions: {type: "array", items: {type: "object"}}}}),
+    requestBody: [
+      {
+        "name": "padID",
+        "in": "query",
+        "schema": {
+          "type": "string"
+        }
+      }
+    ],
+    summary: 'Lists all saved revisions of a pad',
+    tags: ['pad']
+  }
+
+
+  mapping.get(POST)!["/savedRevisions"] = {
+    apiVersion: '1.2.11', functionName: 'saveRevision',
+    requestBody: {
+      content: {
+        "application/json": {
+          schema: {
+            type: "object",
+            properties: {
+              padID: {
+                type: "string"
+              },
+              rev: {
+                type: "integer"
+              }
+            },
+            required: ["padID", "rev"]
+          }
+        }
+      }
+    },
+    responses: prepareResponses({type: "object", properties: {}}),
+    summary: 'Saves a revision of a pad',
+    tags: ['pad']
+  }
+
+  mapping.get(GET)!["/savedRevisions/revisionsCount"] = {
+    apiVersion: '1.2.11', functionName: 'getSavedRevisionsCount',
+    responses: prepareResponses({type: "object", properties: {revisionsCount: {type: "integer"}}}),
+    requestBody: [
+      {
+        "name": "padID",
+        "in": "query",
+        "schema": {
+          "type": "string"
+        }
+      }
+    ],
+    summary: 'Get the number of saved revisions of a pad',
+    tags: ['pad']
+  }
+
+  // Version 1.2.12
+  mapping.get(PATCH)!["/chats/messages"] = {
+    apiVersion: '1.2.12', functionName: 'appendChatMessage',
+    requestBody: {
+      content: {
+        "application/json": {
+          schema: {
+            type: "object",
+            properties: {
+              padID: {
+                type: "string"
+              },
+              text: {
+                type: "string"
+              },
+              authorID: {
+                type: "string"
+              },
+              time: {
+                type: "string"
+              }
+            },
+            required: ["padID", "text"]
+          }
+        }
+      }
+    },
+    responses: prepareResponses({type: "object", properties: {}}),
+    summary: 'Appends a chat message to a pad',
+    tags: ['pad']
+  }
+
+  // Version 1.2.13
+
+  // Version 1.2.14
+  mapping.get(GET)!["/stats"] = {
+    apiVersion: '1.2.14', functionName: 'getStats',
+    responses: prepareResponses({type: "object", properties: {stats: {type: "object"}}}),
+    summary: 'Get stats',
+    tags: ['stats']
+  }
+
+  // Version 1.2.15
+
+  // Version 1.3.0
+  mapping.get(PATCH)!["/pads/text"] = {
+    apiVersion: '1.3.0', functionName: 'appendText',
+    requestBody: {
+      content: {
+        "application/json": {
+          schema: {
+            type: "object",
+            properties: {
+              padID: {
+                type: "string"
+              },
+              text: {
+                type: "string"
+              },
+              authorID: {
+                type: "string"
+              },
+            },
+            required: ["padID", "text", "authorID"]
+          }
+        }
+      }
+    },
+    responses: prepareResponses({type: "object", properties: {}}),
+    summary: 'Appends text to a pad',
+    tags: ['pad']
+  }
+  mapping.get(POST)!["/pads/copyWithoutHistory"] = {
+    apiVersion: '1.3.0', functionName: 'copyPadWithoutHistory',
+    requestBody: {
+      content: {
+        "application/json": {
+          schema: {
+            type: "object",
+            properties: {
+              sourceID: {
+                type: "string"
+              },
+              destinationID: {
+                type: "string"
+              },
+              force: {
+                type: "string"
+              },
+              authorID: {
+                type: "string"
+              }
+            },
+            required: ["sourceID", "destinationID", "force", "authorID"]
+          }
+        }
+      }
+    },
+    responses: prepareResponses({type: "object", properties: {}}),
+    summary: 'Copies a pad without its history',
+    tags: ['pad']
+  }
+  mapping.get(POST)!["/pads/group"] = {
+    apiVersion: '1.3.0', functionName: 'createGroupPad',
+    requestBody: {
+      content: {
+        "application/json": {
+          schema: {
+            type: "object",
+            properties: {
+              groupID: {
+                type: "string"
+              },
+              padName: {
+                type: "string"
+              },
+              text: {
+                type: "string"
+              },
+              authorID: {
+                type: "string"
+              }
+            },
+            required: ["groupID", "padName"]
+          }
+        }
+      }
+    },
+    responses: prepareResponses({type: "object", properties: {}}),
+    summary: 'Creates a new pad in a group',
+    tags: ['pad']
+
+  }
+  mapping.get(POST)!["/pads"] = {
+    apiVersion: '1.3.0', functionName: 'createPad',
+    requestBody: {
+      content: {
+        "application/json": {
+          schema: {
+            type: "object",
+            properties: {
+              padID: {
+                type: "string"
+              },
+              text: {
+                type: "string"
+              },
+              authorId: {
+                type: "string"
+              }
+            },
+            required: ["padName"]
+          }
+        }
+      }
+    },
+    responses: prepareResponses({type: "object", properties: {}}),
+    summary: 'Creates a new pad',
+    tags: ['pad']
+  }
+  mapping.get(PATCH)!["/savedRevisions"] = {
+    apiVersion: '1.3.0', functionName: 'restoreRevision',
+    requestBody: {
+      content: {
+        "application/json": {
+          schema: {
+            type: "object",
+            properties: {
+              padID: {
+                type: "string"
+              },
+              rev: {
+                type: "integer"
+              },
+              authorId: {
+                type: "string"
+              }
+            },
+            required: ["padID", "rev", "authorId"]
+          }
+        }
+      }
+    },
+    responses: prepareResponses({type: "object", properties: {}}),
+    summary: 'Restores a revision of a pad',
+    tags: ['pad']
+  }
+
+
+  mapping.get(POST)!["/pads/html"] = {
+    apiVersion: '1.3.0', functionName: 'setHTML',
+    requestBody: {
+      content: {
+        "application/json": {
+          schema: {
+            type: "object",
+            properties: {
+              padID: {
+                type: "string"
+              },
+              html: {
+                type: "string"
+              },
+              authorId: {
+                type: "string"
+              }
+            },
+            required: ["padID", "html", "authorId"]
+          }
+        }
+      }
+    },
+    responses: prepareResponses({type: "object", properties: {}}),
+    summary: 'Sets the HTML of a pad',
+    tags: ['pad']
+  }
+
+  mapping.get(POST)!["/pads/text"] = {
+    apiVersion: '1.3.0', functionName: 'setText',
+    requestBody: {
+      content: {
+        "application/json": {
+          schema: {
+            type: "object",
+            properties: {
+              padID: {
+                type: "string"
+              },
+              text: {
+                type: "string"
+              },
+              authorId: {
+                type: "string"
+              }
+            },
+            required: ["padID", "text", "authorId"]
+          }
+        }
+      }
+    },
+    responses: prepareResponses({type: "object", properties: {}}),
+    summary: 'Sets the text of a pad',
+    tags: ['pad']
+  }
+
+
+  app.use('/api-docs', serve);
+  app.get('/api-docs', setup(undefined, {
+    swaggerOptions: {
+      url: '/api-docs.json',
+    },
+  }));
+
+  app.use(express.json());
+
+  app.get('/api-docs.json', (req, res) => {
+    const fullUrl = req.protocol + '://' + req.get('host');
+    const generatedDefinition = prepareDefinition(mapping, fullUrl)
+    res.json(generatedDefinition)
+  })
+  app.use('/api/2', async (req, res, next) => {
+    const method = req.method
+    const pathToFunction = req.path
+    // parse fields from request
+    const {headers, params, query} = req;
+
+    // read form data if method was POST
+    let formData: MapArrayType<any> = {};
+    if (method.toLowerCase() === 'post' || method.toLowerCase() === "delete") {
+      if (!req.headers['content-type'] || req.headers['content-type']!.startsWith('application/json')) {
+        // parse json
+        formData = req.body;
+      } else {
+        const form = new IncomingForm();
+        formData = (await form.parse(req))[0];
+        for (const k of Object.keys(formData)) {
+          if (formData[k] instanceof Array) {
+            formData[k] = formData[k][0];
+          }
+        }
+      }
+    }
+
+    const fields = Object.assign({}, headers, params, query, formData);
+
+    if (mapping.has(method) && pathToFunction in mapping.get(method)!) {
+      const {apiVersion, functionName} = mapping.get(method)![pathToFunction]!
+      // pass to api handler
+      let response;
+      try {
+        try {
+          let data = await apiHandler.handle(apiVersion, functionName, fields, req, res);
+
+          // return in common format
+          response = {code: 0, message: 'ok', data: data || null};
+        } catch (err) {
+          const errCaused = err as ErrorCaused
+          // convert all errors to http errors
+          if (createHTTPError.isHttpError(err)) {
+            // pass http errors thrown by handler forward
+            throw err;
+          } else if (errCaused.name === 'apierror') {
+            // parameters were wrong and the api stopped execution, pass the error
+            // convert to http error
+            throw new createHTTPError.BadRequest(errCaused.message);
+          } else {
+            // an unknown error happened
+            // log it and throw internal error
+            console.error(errCaused.stack || errCaused.toString());
+            throw new createHTTPError.InternalServerError('internal error');
+          }
+        }
+      } catch (err) {
+        const errCaused = err as ErrorCaused
+        // handle http errors
+        // @ts-ignore
+        res.statusCode = errCaused.statusCode || 500;
+
+        // convert to our json response format
+        // https://github.com/ether/etherpad-lite/tree/master/doc/api/http_api.md#response-format
+        switch (res.statusCode) {
+          case 403: // forbidden
+            response = {code: 4, message: errCaused.message, data: null};
+            break;
+          case 401: // unauthorized (no or wrong api key)
+            response = {code: 4, message: errCaused.message, data: null};
+            break;
+          case 404: // not found (no such function)
+            response = {code: 3, message: errCaused.message, data: null};
+            break;
+          case 500: // server error (internal error)
+            response = {code: 2, message: errCaused.message, data: null};
+            break;
+          case 400: // bad request (wrong parameters)
+            // respond with 200 OK to keep old behavior and pass tests
+            res.statusCode = 200; // @TODO: this is bad api design
+            response = {code: 1, message: errCaused.message, data: null};
+            break;
+          default:
+            response = {code: 1, message: errCaused.message, data: null};
+            break;
+        }
+      }
+
+
+      console.debug(`RESPONSE, ${functionName}, ${JSON.stringify(response)}`);
+
+      // return the response data
+      res.json(response);
+    } else {
+      res.json({code: 1, message: 'not found'});
+    }
+  })
+}
diff --git a/src/node/hooks/express/adminsettings.ts b/src/node/hooks/express/adminsettings.ts
index 63d901f212618da93a31d75a31d9dfb2dae17499..4c60a05ad205d47357979107f41671a1431439fe 100644
--- a/src/node/hooks/express/adminsettings.ts
+++ b/src/node/hooks/express/adminsettings.ts
@@ -13,6 +13,7 @@ const settings = require('../../utils/Settings');
 const UpdateCheck = require('../../utils/UpdateCheck');
 const padManager = require('../../db/PadManager');
 const api = require('../../db/API');
+const cleanup = require('../../utils/Cleanup');
 
 
 const queryPadLimit = 12;
@@ -252,6 +253,40 @@ exports.socketio = (hookName: string, {io}: any) => {
             }
         })
 
+        socket.on('cleanupPadRevisions', async (padId: string) => {
+          if (!settings.cleanup.enabled) {
+            socket.emit('results:cleanupPadRevisions', {
+              error: 'Cleanup disabled. Enable cleanup in settings.json: cleanup.enabled => true',
+            });
+            return;
+          }
+
+          const padExists = await padManager.doesPadExists(padId);
+          if (padExists) {
+            logger.info(`Cleanup pad revisions: ${padId}`);
+            try {
+              const result = await cleanup.deleteRevisions(padId, settings.cleanup.keepRevisions)
+              if (result) {
+                socket.emit('results:cleanupPadRevisions', {
+                  padId: padId,
+                  keepRevisions: settings.cleanup.keepRevisions,
+                });
+                logger.info('successful cleaned up pad: ', padId)
+              } else {
+                socket.emit('results:cleanupPadRevisions', {
+                  error: 'Error cleaning up pad',
+                });
+              }
+            } catch (err: any) {
+              logger.error(`Error in pad ${padId}: ${err.stack || err}`);
+              socket.emit('results:cleanupPadRevisions', {
+                error: err.toString(),
+              });
+              return;
+            }
+          }
+        })
+
         socket.on('restartServer', async () => {
             logger.info('Admin request to restart server through a socket on /admin/settings');
             settings.reloadSettings();
diff --git a/src/node/hooks/express/specialpages.ts b/src/node/hooks/express/specialpages.ts
index b2ea3614e8fccec3e275e16fb5cba50d213668ae..ef5914e95736786ae3087b8f6b27363eb54ce03c 100644
--- a/src/node/hooks/express/specialpages.ts
+++ b/src/node/hooks/express/specialpages.ts
@@ -12,6 +12,7 @@ const webaccess = require('./webaccess');
 const plugins = require('../../../static/js/pluginfw/plugin_defs');
 
 import {build, buildSync} from 'esbuild'
+import {ArgsExpressType} from "../../types/ArgsExpressType";
 let ioI: { sockets: { sockets: any[]; }; } | null = null
 
 exports.socketio = (hookName: string, {io}: any) => {
@@ -19,7 +20,7 @@ exports.socketio = (hookName: string, {io}: any) => {
 }
 
 
-exports.expressPreSession = async (hookName:string, {app}:any) => {
+exports.expressPreSession = async (hookName:string, {app}:ArgsExpressType) => {
   // This endpoint is intended to conform to:
   // https://www.ietf.org/archive/id/draft-inadarei-api-health-check-06.html
   app.get('/health', (req:any, res:any) => {
@@ -113,7 +114,7 @@ const convertTypescript = (content: string) => {
 
 const handleLiveReload = async (args: any, padString: string, timeSliderString: string, indexString: any) => {
   const chokidar = await import('chokidar')
-  const watcher = chokidar.watch(path.join(settings.root, 'src', 'static', 'js'));
+  const watcher = chokidar.watch(path.join(settings.root, 'src', 'static', 'js'), {});
   let routeHandlers: { [key: string]: Function } = {};
 
   const setRouteHandler = (path: string, newHandler: Function) => {
@@ -243,7 +244,7 @@ const convertTypescriptWatched = (content: string, cb: (output:string, hash: str
   })
 }
 
-exports.expressCreateServer = async (hookName: string, args: any, cb: Function) => {
+exports.expressCreateServer = async (hookName: string, args: ArgsExpressType, cb: Function) => {
   const padString =   eejs.require('ep_etherpad-lite/templates/padBootstrap.js', {
       pluginModules: (() => {
         const pluginModules = new Set();
diff --git a/src/node/security/OAuth2Provider.ts b/src/node/security/OAuth2Provider.ts
index e212113504b49ac854632415f6aa8eacfc593d1e..76e7ed4b894d55ff11187b6cdc2e2938f3b7a87e 100644
--- a/src/node/security/OAuth2Provider.ts
+++ b/src/node/security/OAuth2Provider.ts
@@ -153,7 +153,7 @@ export const expressCreateServer = async (hookName: string, args: ArgsExpressTyp
     });
 
 
-    args.app.post('/interaction/:uid', async (req: Http2ServerRequest, res: Http2ServerResponse, next:Function) => {
+    args.app.post('/interaction/:uid', async (req, res, next) => {
         const formid = new IncomingForm();
         try {
             // @ts-ignore
@@ -226,7 +226,7 @@ export const expressCreateServer = async (hookName: string, args: ArgsExpressTyp
     })
 
 
-    args.app.get('/interaction/:uid', async (req: Request, res: Response, next: Function) => {
+    args.app.get('/interaction/:uid', async (req, res, next) => {
         try {
             const {
                 uid, prompt, params, session,
diff --git a/src/node/types/ArgsExpressType.ts b/src/node/types/ArgsExpressType.ts
index 5c0675b973099d07e755b263229be5138e5bfa39..d8dc700be1cde24659bc8678657c51e0549d3cc0 100644
--- a/src/node/types/ArgsExpressType.ts
+++ b/src/node/types/ArgsExpressType.ts
@@ -1,5 +1,7 @@
+import {Express} from "express";
+
 export type ArgsExpressType = {
-    app:any,
+    app:Express,
     io: any,
     server:any
-}
\ No newline at end of file
+}
diff --git a/src/node/types/Revision.ts b/src/node/types/Revision.ts
new file mode 100644
index 0000000000000000000000000000000000000000..8a9d65e29cf319ba75dfcaaec90f60d7a11af4d6
--- /dev/null
+++ b/src/node/types/Revision.ts
@@ -0,0 +1,9 @@
+import {AChangeSet} from "./PadType";
+
+export type Revision = {
+  changeset: AChangeSet,
+  meta: {
+    author: string,
+    timestamp: number,
+  }
+}
diff --git a/src/node/utils/Cleanup.ts b/src/node/utils/Cleanup.ts
new file mode 100644
index 0000000000000000000000000000000000000000..7e480020dda6aec8c52cec34078de5b7e96c80c7
--- /dev/null
+++ b/src/node/utils/Cleanup.ts
@@ -0,0 +1,168 @@
+'use strict'
+
+import {AChangeSet} from "../types/PadType";
+import {Revision} from "../types/Revision";
+
+const promises = require('./promises');
+const padManager = require('ep_etherpad-lite/node/db/PadManager');
+const db = require('ep_etherpad-lite/node/db/DB');
+const Changeset = require('ep_etherpad-lite/static/js/Changeset');
+const padMessageHandler = require('ep_etherpad-lite/node/handler/PadMessageHandler');
+const log4js = require('log4js');
+const logger = log4js.getLogger('cleanup');
+
+exports.deleteAllRevisions = async (padID: string): Promise<void> => {
+
+  const randomPadId = padID + 'aertdfdf' + Math.random().toString(10)
+
+  let pad = await padManager.getPad(padID);
+  await pad.copyPadWithoutHistory(randomPadId, false);
+  pad = await padManager.getPad(randomPadId);
+  await pad.copyPadWithoutHistory(padID, true);
+  await pad.remove();
+}
+
+const createRevision = async (aChangeset: AChangeSet, timestamp: number, isKeyRev: boolean, authorId: string, atext: any, pool: any) => {
+
+  if (authorId !== '') pool.putAttrib(['author', authorId]);
+
+  return {
+    changeset: aChangeset,
+    meta: {
+      author: authorId,
+      timestamp: timestamp,
+      ...isKeyRev ? {
+        pool: pool,
+        atext: atext,
+      } : {},
+    },
+  };
+}
+
+exports.deleteRevisions = async (padId: string, keepRevisions: number): Promise<boolean> => {
+
+  logger.debug('Start cleanup revisions', padId)
+
+  let pad = await padManager.getPad(padId);
+  await pad.check()
+
+  logger.debug('Initial pad is valid')
+
+  if (pad.head <= keepRevisions) {
+    logger.debug('Pad has not enough revisions')
+    return false
+  }
+
+  padMessageHandler.kickSessionsFromPad(padId)
+
+  const cleanupUntilRevision = pad.head - keepRevisions
+  logger.debug('Composing changesets: ', cleanupUntilRevision)
+  const changeset = await padMessageHandler.composePadChangesets(pad, 0, cleanupUntilRevision + 1)
+
+  const revisions: Revision[] = [];
+
+  await promises.timesLimit(keepRevisions + 1, 500, async (i: number) => {
+    const rev = i + cleanupUntilRevision
+    revisions[rev] = await pad.getRevision(rev)
+  });
+
+  logger.debug('Loaded revisions: ', revisions.length)
+
+  await promises.timesLimit(pad.head + 1, 500, async (i: string) => {
+    await db.remove(`pad:${padId}:revs:${i}`, null);
+  });
+
+  let padContent = await db.get(`pad:${padId}`)
+  padContent.head = keepRevisions
+  if (padContent.savedRevisions) {
+    let newSavedRevisions = []
+
+    for (let i = 0; i < padContent.savedRevisions.length; i++) {
+      if (padContent.savedRevisions[i].revNum > cleanupUntilRevision) {
+        padContent.savedRevisions[i].revNum = padContent.savedRevisions[i].revNum - cleanupUntilRevision
+        newSavedRevisions.push(padContent.savedRevisions[i])
+      }
+    }
+    padContent.savedRevisions = newSavedRevisions
+  }
+  await db.set(`pad:${padId}`, padContent);
+
+  let newAText = Changeset.makeAText('\n');
+  let pool = pad.apool()
+
+  newAText = Changeset.applyToAText(changeset, newAText, pool);
+
+  const revision = await createRevision(
+    changeset,
+    revisions[cleanupUntilRevision].meta.timestamp,
+    0 === pad.getKeyRevisionNumber(0),
+    '',
+    newAText,
+    pool
+  );
+
+  const p: Promise<void>[] = [];
+
+  p.push(db.set(`pad:${padId}:revs:0`, revision))
+
+  p.push(promises.timesLimit(keepRevisions, 500, async (i: number) => {
+    const rev = i + cleanupUntilRevision + 1
+    const newRev = rev - cleanupUntilRevision;
+
+    newAText = Changeset.applyToAText(revisions[rev].changeset, newAText, pool);
+
+    const revision = await createRevision(
+      revisions[rev].changeset,
+      revisions[rev].meta.timestamp,
+      newRev === pad.getKeyRevisionNumber(newRev),
+      revisions[rev].meta.author,
+      newAText,
+      pool
+    );
+
+    await db.set(`pad:${padId}:revs:${newRev}`, revision);
+  }));
+
+  await Promise.all(p)
+
+  logger.debug('Finished migration. Checking pad now')
+
+  padManager.unloadPad(padId);
+
+  let newPad = await padManager.getPad(padId);
+  await newPad.check();
+
+  return true
+}
+
+exports.checkTodos = async () => {
+  await new Promise(resolve => setTimeout(resolve, 5000));
+
+  // TODO: Move to settings
+  const settings = {
+    minHead: 100,
+    keepRevisions: 100,
+    minAge: 1,//1000 * 60 * 60 * 24,
+  }
+
+  await Promise.all((await padManager.listAllPads()).padIDs.map(async (padId: string) => {
+    // TODO: Handle concurrency
+    const pad = await padManager.getPad(padId);
+
+    const revisionDate = await pad.getRevisionDate(pad.getHeadRevisionNumber())
+
+    if (pad.head < settings.minHead || padMessageHandler.padUsersCount(padId) > 0 || Date.now() < revisionDate + settings.minAge) {
+      return
+    }
+
+    try {
+      const result = await exports.deleteRevisions(padId, settings.keepRevisions)
+      if (result) {
+        logger.info('successful cleaned up pad: ', padId)
+      }
+    } catch (err: any) {
+      logger.error(`Error in pad ${padId}: ${err.stack || err}`);
+      return;
+    }
+  }));
+}
diff --git a/src/node/utils/Settings.ts b/src/node/utils/Settings.ts
index 00b9c2981f918d93c74411a2bb470e5a02096b9a..4d7b421e1c8817cec2b2379023d7a8ab44d12359 100644
--- a/src/node/utils/Settings.ts
+++ b/src/node/utils/Settings.ts
@@ -107,6 +107,7 @@ exports.ttl = {
     RefreshToken: 1 * 24 * 60 * 60, // 1 day in seconds
 }
 
+exports.updateServer = "https://static.etherpad.org"
 
 
 /*
@@ -379,6 +380,14 @@ exports.sso = {
  */
 exports.showSettingsInAdminPage = true;
 
+/*
+ * Settings for cleanup of pads
+ */
+exports.cleanup = {
+  enabled: false,
+  keepRevisions: 100,
+}
+
 /*
  * By default, when caret is moved out of viewport, it scrolls the minimum
  * height needed to make this line visible.
diff --git a/src/node/utils/UpdateCheck.ts b/src/node/utils/UpdateCheck.ts
index 534c5c640faf41432e73d4adce0aada634f07931..de7d2eea62f9fed7d20d01dbef5af8cbe7738a15 100644
--- a/src/node/utils/UpdateCheck.ts
+++ b/src/node/utils/UpdateCheck.ts
@@ -20,7 +20,7 @@ const loadEtherpadInformations = () => {
     return infos;
   }
 
-  return axios.get('https://static.etherpad.org/info.json', {headers: headers})
+  return axios.get(`${settings.updateServer}/info.json`, {headers: headers})
   .then(async (resp: any) => {
     infos = await resp.data;
     if (infos === undefined || infos === null) {
diff --git a/src/package.json b/src/package.json
index 3a6d232a010516c23fe0fd77be5b015bf7a3b7ab..4ebb80a297ff5db86f8a75854a6ecdfe2c937a4e 100644
--- a/src/package.json
+++ b/src/package.json
@@ -38,13 +38,13 @@
     "cross-spawn": "^7.0.3",
     "ejs": "^3.1.10",
     "esbuild": "^0.23.1",
-    "express": "4.19.2",
+    "express": "4.21.0",
     "express-rate-limit": "^7.4.0",
     "fast-deep-equal": "^3.1.3",
     "find-root": "1.1.0",
     "formidable": "^3.5.1",
     "http-errors": "^2.0.0",
-    "jose": "^5.8.0",
+    "jose": "^5.9.2",
     "js-cookie": "^3.0.5",
     "jsdom": "^25.0.0",
     "jsonminify": "0.4.2",
@@ -57,31 +57,32 @@
     "measured-core": "^2.0.0",
     "mime-types": "^2.1.35",
     "oidc-provider": "^8.5.1",
-    "openapi-backend": "^5.10.6",
+    "openapi-backend": "^5.11.0",
     "proxy-addr": "^2.0.7",
     "rate-limiter-flexible": "^5.0.3",
     "rehype": "^13.0.1",
-    "rehype-minify-whitespace": "^6.0.0",
+    "rehype-minify-whitespace": "^6.0.1",
     "resolve": "1.22.8",
+    "rusty-store-kv": "^1.3.1",
     "security": "1.0.0",
     "semver": "^7.6.3",
     "socket.io": "^4.7.5",
     "socket.io-client": "^4.7.5",
     "superagent": "10.1.0",
+    "swagger-ui-express": "^5.0.1",
     "tinycon": "0.6.8",
-    "tsx": "4.19.0",
-    "ueberdb2": "^4.2.103",
+    "tsx": "4.19.1",
+    "ueberdb2": "^5.0.2",
     "underscore": "1.13.7",
     "unorm": "1.6.0",
-    "wtfnode": "^0.9.3",
-    "rusty-store-kv": "^1.2.0"
+    "wtfnode": "^0.9.3"
   },
   "bin": {
     "etherpad-healthcheck": "../bin/etherpad-healthcheck",
     "etherpad-lite": "node/server.ts"
   },
   "devDependencies": {
-    "@playwright/test": "^1.47.0",
+    "@playwright/test": "^1.47.1",
     "@types/async": "^3.2.24",
     "@types/express": "^4.17.21",
     "@types/formidable": "^3.4.5",
@@ -89,17 +90,19 @@
     "@types/jquery": "^3.5.30",
     "@types/js-cookie": "^3.0.6",
     "@types/jsdom": "^21.1.7",
-    "@types/jsonwebtoken": "^9.0.6",
+    "@types/jsonwebtoken": "^9.0.7",
     "@types/mime-types": "^2.1.4",
-    "@types/mocha": "^10.0.7",
-    "@types/node": "^22.5.4",
+    "@types/mocha": "^10.0.8",
+    "@types/node": "^22.5.5",
     "@types/oidc-provider": "^8.5.2",
     "@types/semver": "^7.5.8",
     "@types/sinon": "^17.0.3",
     "@types/supertest": "^6.0.2",
+    "@types/swagger-ui-express": "^4.1.6",
     "@types/underscore": "^1.11.15",
-    "chokidar": "^3.6.0",
-    "eslint": "^9.9.1",
+    "@types/whatwg-mimetype": "^3.0.2",
+    "chokidar": "^4.0.0",
+    "eslint": "^9.10.0",
     "eslint-config-etherpad": "^4.0.4",
     "etherpad-cli-client": "^3.0.2",
     "mocha": "^10.7.3",
@@ -107,11 +110,11 @@
     "nodeify": "^1.0.1",
     "openapi-schema-validation": "^0.4.2",
     "set-cookie-parser": "^2.7.0",
-    "sinon": "^18.0.0",
+    "sinon": "^19.0.2",
     "split-grid": "^1.0.11",
     "supertest": "^7.0.0",
-    "typescript": "^5.5.4",
-    "vitest": "^2.0.5"
+    "typescript": "^5.6.2",
+    "vitest": "^2.1.1"
   },
   "engines": {
     "node": ">=18.18.2",
@@ -138,6 +141,6 @@
     "debug:socketio": "cross-env DEBUG=socket.io* node --require tsx/cjs node/server.ts",
     "test:vitest": "vitest"
   },
-  "version": "2.2.4",
+  "version": "2.2.5",
   "license": "Apache-2.0"
 }
diff --git a/src/static/js/ace2_inner.ts b/src/static/js/ace2_inner.ts
index 0dce038a89ca340c218fb1f1a6c01a7694df3155..15db776e1a384666dbe57a5116e349b3b94f2408 100644
--- a/src/static/js/ace2_inner.ts
+++ b/src/static/js/ace2_inner.ts
@@ -167,7 +167,7 @@ function Ace2Inner(editorInfo, cssManagers) {
     for (const name of names) console[name] = noop;
   }
 
-  const scheduler = parent; // hack for opera required
+  const scheduler = window; // hack for opera required
 
   const performDocumentReplaceRange = (start, end, newText) => {
     if (start === undefined) start = rep.selStart;
@@ -240,7 +240,7 @@ function Ace2Inner(editorInfo, cssManagers) {
         bgcolor = fadeColor(bgcolor, info.fade);
       }
       const textColor =
-          colorutils.textColorFromBackgroundColor(bgcolor, parent.parent.clientVars.skinName);
+          colorutils.textColorFromBackgroundColor(bgcolor, window.clientVars.skinName);
       const styles = [
         cssManagers.inner.selectorStyle(authorSelector),
         cssManagers.parent.selectorStyle(authorSelector),
@@ -1270,7 +1270,7 @@ function Ace2Inner(editorInfo, cssManagers) {
       const prevLine = rep.lines.prev(thisLine);
       const prevLineText = prevLine.text;
       let theIndent = /^ *(?:)/.exec(prevLineText)[0];
-      const shouldIndent = parent.parent.clientVars.indentationOnNewLine;
+      const shouldIndent = window.clientVars.indentationOnNewLine;
       if (shouldIndent && /[[(:{]\s*$/.exec(prevLineText)) {
         theIndent += THE_TAB;
       }
@@ -2023,7 +2023,7 @@ function Ace2Inner(editorInfo, cssManagers) {
   const isPadLoading = (t) => t === 'setup' || t === 'setBaseText' || t === 'importText';
 
   const updateStyleButtonState = (attribName, hasStyleOnRepSelection) => {
-    const $formattingButton = parent.parent.$(`[data-key="${attribName}"]`).find('a');
+    const $formattingButton = window.$(`[data-key="${attribName}"]`).find('a');
     $formattingButton.toggleClass(SELECT_BUTTON_CLASS, hasStyleOnRepSelection);
   };
 
@@ -2277,7 +2277,7 @@ function Ace2Inner(editorInfo, cssManagers) {
   };
 
   const hideEditBarDropdowns = () => {
-    window.parent.parent.padeditbar.toggleDropDown('none');
+    window.padeditbar.toggleDropDown('none');
   };
 
   const renumberList = (lineNum) => {
@@ -2582,7 +2582,7 @@ function Ace2Inner(editorInfo, cssManagers) {
           specialHandled = specialHandledInHook.indexOf(true) !== -1;
         }
 
-        const padShortcutEnabled = parent.parent.clientVars.padShortcutEnabled;
+        const padShortcutEnabled = window.clientVars.padShortcutEnabled;
         if (!specialHandled && isTypeForSpecialKey &&
             altKey && keyCode === 120 &&
             padShortcutEnabled.altF9) {
@@ -2591,7 +2591,7 @@ function Ace2Inner(editorInfo, cssManagers) {
           // As ubuntu cannot use Alt F10....
           // Focus on the editbar.
           // -- TODO: Move Focus back to previous state (we know it so we can use it)
-          const firstEditbarElement = parent.parent.$('#editbar')
+          const firstEditbarElement = window.$('#editbar')
               .children('ul').first().children().first()
               .children().first().children().first();
           $(this).trigger('blur');
@@ -2603,8 +2603,8 @@ function Ace2Inner(editorInfo, cssManagers) {
             padShortcutEnabled.altC) {
           // Alt c focuses on the Chat window
           $(this).trigger('blur');
-          parent.parent.chat.show();
-          parent.parent.$('#chatinput').trigger('focus');
+          window.chat.show();
+          window.$('#chatinput').trigger('focus');
           evt.preventDefault();
         }
         if (!specialHandled && type === 'keydown' &&
@@ -2626,12 +2626,12 @@ function Ace2Inner(editorInfo, cssManagers) {
               if (authorId) authorIds.add(authorId);
             }
           }
-          const idToName = new Map(parent.parent.pad.userList().map((a) => [a.userId, a.name]));
-          const myId = parent.parent.clientVars.userId;
+          const idToName = new Map(window.pad.userList().map((a) => [a.userId, a.name]));
+          const myId = window.clientVars.userId;
           const authors =
               [...authorIds].map((id) => id === myId ? 'me' : idToName.get(id) || 'unknown');
 
-          parent.parent.$.gritter.add({
+          window.$.gritter.add({
             title: 'Line Authors',
             text:
                 authors.length === 0 ? 'No author information is available'
@@ -2680,7 +2680,7 @@ function Ace2Inner(editorInfo, cssManagers) {
           specialHandled = true;
 
           // close all gritters when the user hits escape key
-          parent.parent.$.gritter.removeAll();
+          window.$.gritter.removeAll();
         }
         if (!specialHandled && isTypeForCmdKey &&
             /* Do a saved revision on ctrl S */
@@ -2688,13 +2688,13 @@ function Ace2Inner(editorInfo, cssManagers) {
             !evt.altKey &&
             padShortcutEnabled.cmdS) {
           evt.preventDefault();
-          const originalBackground = parent.parent.$('#revisionlink').css('background');
-          parent.parent.$('#revisionlink').css({background: 'lightyellow'});
+          const originalBackground = window.$('#revisionlink').css('background');
+          window.$('#revisionlink').css({background: 'lightyellow'});
           scheduler.setTimeout(() => {
-            parent.parent.$('#revisionlink').css({background: originalBackground});
+            window.$('#revisionlink').css({background: originalBackground});
           }, 1000);
-          /* The parent.parent part of this is BAD and I feel bad..  It may break something */
-          parent.parent.pad.collabClient.sendMessage({type: 'SAVE_REVISION'});
+
+          window.pad.collabClient.sendMessage({type: 'SAVE_REVISION'});
           specialHandled = true;
         }
         if (!specialHandled && isTypeForSpecialKey &&
diff --git a/src/static/js/broadcast.ts b/src/static/js/broadcast.ts
index 85ccdb0ff6d0752e2fd4eff6e7ded3c0812599ba..347cf29e29e575e72934c42d90f5f088c31f82da 100644
--- a/src/static/js/broadcast.ts
+++ b/src/static/js/broadcast.ts
@@ -186,7 +186,7 @@ const loadBroadcastJS = (socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
 
     mutateTextLines(changeset, padContents);
     padContents.currentRevision = revision;
-    padContents.currentTime += timeDelta * 1000;
+    padContents.currentTime += timeDelta;
 
     updateTimer();
 
@@ -299,7 +299,7 @@ const loadBroadcastJS = (socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
       // Loading changeset history for new revision
       loadChangesetsForRevision(newRevision, update);
       // Loading changeset history for old revision (to make diff between old and new revision)
-      loadChangesetsForRevision(padContents.currentRevision - 1);
+      loadChangesetsForRevision(padContents.currentRevision);
     }
 
     const authors = _.map(padContents.getActiveAuthors(), (name) => authorData[name]);
diff --git a/src/static/js/changesettracker.ts b/src/static/js/changesettracker.ts
index 41d5c266891d997c027da4899f630cc2ed326e5d..a8d19945d23ebd4e06375cb33984ebdce3d6b12e 100644
--- a/src/static/js/changesettracker.ts
+++ b/src/static/js/changesettracker.ts
@@ -140,7 +140,7 @@ const makeChangesetTracker = (scheduler, apool, aceCallbacksProvider) => {
         toSubmit = compose(submittedChangeset, userChangeset, apool);
       } else {
         // Get my authorID
-        const authorId = parent.parent.pad.myUserInfo.userId;
+        const authorId = window.pad.myUserInfo.userId;
 
         // Sanitize authorship: Replace all author attributes with this user's author ID in case the
         // text was copied from another author.
diff --git a/src/static/js/pluginfw/installer.ts b/src/static/js/pluginfw/installer.ts
index effed768a804dfbee753d7de323125cdc7d03adb..c605378e1bda2fba536b1414c7b205f0594f5ef0 100644
--- a/src/static/js/pluginfw/installer.ts
+++ b/src/static/js/pluginfw/installer.ts
@@ -171,7 +171,7 @@ export const getAvailablePlugins = (maxCacheAge: number|false) => {
       return resolve(availablePlugins);
     }
 
-    await axios.get('https://static.etherpad.org/plugins.json', {headers})
+    await axios.get(`${settings.updateServer}/plugins.json`, {headers})
         .then((pluginsLoaded:AxiosResponse<MapArrayType<PackageInfo>>) => {
           availablePlugins = pluginsLoaded.data;
           cacheTimestamp = nowTimestamp;
diff --git a/src/static/js/scroll.ts b/src/static/js/scroll.ts
index 95075d8078dfee876145259b3425adf48e902e95..74c2462434e39b3b0357a0a673b9b4283f59e62d 100644
--- a/src/static/js/scroll.ts
+++ b/src/static/js/scroll.ts
@@ -15,7 +15,8 @@ class Scroll {
     // DOM reference
     this.outerWin = outerWin;
     this.doc = this.outerWin.contentDocument!;
-    this.rootDocument = parent.parent.document;
+    this.rootDocument = document;
+    console.log(this.rootDocument)
   }
 
   scrollWhenCaretIsInTheLastLineOfViewportWhenNecessary(rep: RepModel, isScrollableEvent: boolean, innerHeight: number) {
@@ -112,7 +113,7 @@ class Scroll {
   };
 
   _getEditorPositionTop() {
-    const editor = parent.document.getElementsByTagName('iframe');
+    const editor = document.getElementsByTagName('iframe');
     const editorPositionTop = editor[0].offsetTop;
     return editorPositionTop;
   };
diff --git a/ui/package.json b/ui/package.json
index f39e405251d7cb2e3587e2d84dd1d8b41847663a..d10a992e69ad5ae94af82bdbf91ac7e19d64356e 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -11,7 +11,7 @@
   },
   "devDependencies": {
     "ep_etherpad-lite": "workspace:../src",
-    "typescript": "^5.5.4",
-    "vite": "^5.4.3"
+    "typescript": "^5.6.2",
+    "vite": "^5.4.7"
   }
 }