Repository: evcc-io/evcc
Pull request #8774 (2023-07-03 09:28:57)

Seat: refer to official api generations
Refs https://github.com/evcc-io/evcc/issues/8748#issuecomment-1617288720


Patch:
diff --git a/.goreleaser.yml b/.goreleaser.yml
index 5ac7c078..c03578a0 100644
--- a/.goreleaser.yml
+++ b/.goreleaser.yml
@@ -87,7 +87,7 @@ nfpms:
       postremove: ./packaging/scripts/postremove.sh
 
 brews:
-  - tap:
+  - repository:
       owner: evcc-io
       name: homebrew-tap
     commit_author:
diff --git a/.prettierrc.js b/.prettierrc.js
index 5f9a0815..ff9f57ba 100644
--- a/.prettierrc.js
+++ b/.prettierrc.js
@@ -1,3 +1,4 @@
 module.exports = {
   printWidth: 100,
+  trailingComma: "es5"
 };
diff --git a/assets/index.html b/assets/index.html
index d36eb099..e65606ca 100644
--- a/assets/index.html
+++ b/assets/index.html
@@ -1,4 +1,4 @@
-<!DOCTYPE html>
+<!doctype html>
 <html lang="en">
   <head>
     <meta charset="utf-8" />
diff --git a/assets/js/components/CustomSelect.vue b/assets/js/components/CustomSelect.vue
new file mode 100644
index 00000000..b214dbeb
--- /dev/null
+++ b/assets/js/components/CustomSelect.vue
@@ -0,0 +1,48 @@
+<template>
+	<label class="position-relative d-block">
+		<select :value="selected" class="custom-select" @change="change">
+			<option
+				v-for="{ name, value, count } in options"
+				:key="value"
+				:value="value"
+				:disabled="count === 0"
+			>
+				{{ text(name, count) }}
+			</option>
+		</select>
+		<slot></slot>
+	</label>
+</template>
+
+<script>
+export default {
+	name: "CustomSelect",
+	props: {
+		options: { type: Array },
+		selected: { type: String },
+	},
+	emits: ["change"],
+	methods: {
+		text(name, count) {
+			if (count === undefined) {
+				return name;
+			}
+			return `${name} (${count})`;
+		},
+		change(event) {
+			this.$emit("change", event);
+		},
+	},
+};
+</script>
+<style scoped>
+.custom-select {
+	left: 0;
+	top: 0;
+	bottom: 0;
+	right: 0;
+	position: absolute;
+	opacity: 0;
+	-webkit-appearance: menulist-button;
+}
+</style>
diff --git a/assets/js/components/LoadpointSettingsModal.vue b/assets/js/components/LoadpointSettingsModal.vue
index bfe79bbc..9a4cb553 100644
--- a/assets/js/components/LoadpointSettingsModal.vue
+++ b/assets/js/components/LoadpointSettingsModal.vue
@@ -258,7 +258,7 @@ export default {
 		currentOptions: function (max, defaultCurrent = 16) {
 			const result = [];
 			const toValue = max ? 32 : this.maxCurrent;
-			const fromValue = max ? this.minCurrent : 6;
+			const fromValue = max ? this.minCurrent : 1;
 			for (let value = toValue; value >= fromValue; value--) {
 				let name = `${value} A`;
 				if (value === defaultCurrent) {
diff --git a/assets/js/components/Savings.vue b/assets/js/components/Savings.vue
index af0f7f0b..1bbe2774 100644
--- a/assets/js/components/Savings.vue
+++ b/assets/js/components/Savings.vue
@@ -175,9 +175,8 @@ export default {
 			return Math.round(this.selfConsumptionPercent) || 0;
 		},
 		effectivePriceFormatted() {
-			const [value, unit] = this.fmtPricePerKWh(this.effectivePrice, this.currency).split(
-				" "
-			);
+			const value = this.fmtPricePerKWh(this.effectivePrice, this.currency, false, false);
+			const unit = this.pricePerKWhUnit(this.currency);
 			return { value, unit };
 		},
 		startDate() {
diff --git a/assets/js/i18n.js b/assets/js/i18n.js
index 311e7985..e5ed5ef7 100644
--- a/assets/js/i18n.js
+++ b/assets/js/i18n.js
@@ -7,9 +7,12 @@ import settings from "./settings";
 
 // https://github.com/joker-x/languages.js/blob/master/languages.json
 export const LOCALES = {
+  ar: ["Arabic", "العربية"],
+  bh: ["Bihari", "भोजपुरी"],
+  ca: ["Catalan", "Català"],
+  cs: ["Czech", "Česky"],
   da: ["Danish", "Dansk"],
   de: ["German", "Deutsch"],
-  ca: ["Catalan", "Català"],
   en: ["English", "English"],
   es: ["Spanish", "Español"],
   fi: ["Finnish", "Suomi"],
diff --git a/assets/js/mixins/formatter.js b/assets/js/mixins/formatter.js
index 0d6133d1..f7f55fcb 100644
--- a/assets/js/mixins/formatter.js
+++ b/assets/js/mixins/formatter.js
@@ -197,22 +197,31 @@ export default {
       const symbols = { EUR: "€", USD: "$" };
       return symbols[currency] || currency;
     },
-    fmtPricePerKWh: function (amout = 0, currency = "EUR", short = false) {
-      let unit = currency;
+    fmtPricePerKWh: function (amout = 0, currency = "EUR", short = false, withUnit = true) {
       let value = amout;
       let minimumFractionDigits = 1;
       let maximumFractionDigits = 3;
       if (["EUR", "USD"].includes(currency)) {
         value *= 100;
-        unit = "ct";
         minimumFractionDigits = 1;
         maximumFractionDigits = 1;
       }
-      return `${new Intl.NumberFormat(this.$i18n.locale, {
+      const price = new Intl.NumberFormat(this.$i18n.locale, {
         style: "decimal",
         minimumFractionDigits,
         maximumFractionDigits,
-      }).format(value)} ${unit}${short ? "" : "/kWh"}`;
+      }).format(value);
+      if (withUnit) {
+        return `${price} ${this.pricePerKWhUnit(currency, short)}`;
+      }
+      return price;
+    },
+    pricePerKWhUnit: function (currency = "EUR", short = false) {
+      let unit = currency;
+      if (["EUR", "USD"].includes(currency)) {
+        unit = "ct";
+      }
+      return `${unit}${short ? "" : "/kWh"}`;
     },
     fmtTimeAgo: function (elapsed) {
       const units = {
diff --git a/assets/js/views/App.vue b/assets/js/views/App.vue
index 52983dfe..c6290c22 100644
--- a/assets/js/views/App.vue
+++ b/assets/js/views/App.vue
@@ -1,5 +1,5 @@
 <template>
-	<div class="app overflow-hidden">
+	<div class="app">
 		<metainfo>
 			<template #title="{ content }">{{ content ? `${content} | evcc` : `evcc` }}</template>
 		</metainfo>
diff --git a/assets/js/views/ChargingSessions.vue b/assets/js/views/ChargingSessions.vue
index 4046d39f..4bd19406 100644
--- a/assets/js/views/ChargingSessions.vue
+++ b/assets/js/views/ChargingSessions.vue
@@ -1,18 +1,19 @@
 <template>
 	<div class="container px-4">
 		<header class="d-flex justify-content-between align-items-center py-3">
-			<h1 class="mb-1 pt-1 d-flex text-nowrap">
-				<router-link class="dropdown-item mx-2 me-2" to="/">
-					<shopicon-bold-arrowback size="s" class="back"></shopicon-bold-arrowback>
+			<h1 class="mb-1 pt-1 d-flex text-nowrap text-truncate">
+				<router-link class="evcc-default-text" to="/">
+					<shopicon-regular-home size="s" class="home"></shopicon-regular-home>
 				</router-link>
-				{{ $t("sessions.title") }}
+				<div size="s" class="mx-2 flex-grow-0 flex-shrink-0 fw-normal">/</div>
+				<span class="text-truncate">{{ $t("sessions.title") }}</span>
 			</h1>
 			<TopNavigation />
 		</header>
 
 		<div class="row">
 			<main class="col-12">
-				<div class="d-flex align-items-baseline justify-content-between my-5">
+				<div class="d-flex align-items-baseline justify-content-between my-3 my-md-5">
 					<router-link
 						class="d-flex text-decoration-none align-items-center"
 						:class="{ 'pe-none': !hasPrev, 'text-muted': !hasPrev }"
@@ -42,9 +43,9 @@
 				<div v-if="currentSessions.length === 0" data-testid="sessions-nodata" class="my-5">
 					<p>{{ $t("sessions.noData") }}</p>
 				</div>
-				<div v-else class="table-responsive my-3">
+				<div v-else class="my-3 my-md-5 table-outer">
 					<table class="table text-nowrap">
-						<thead>
+						<thead class="sticky-top">
 							<tr data-testid="sessions-head">
 								<th scope="col" class="align-top ps-0">
 									{{ $t("sessions.date") }}
@@ -52,114 +53,154 @@
 								<th
 									v-if="showLoadpoints"
 									scope="col"
-									class="align-top"
+									class="align-top d-none d-md-table-cell"
 									data-testid="loadpoint"
 								>
 									{{ $t("sessions.loadpoint") }}
-									<label class="position-relative d-block">
-										<select
-											:value="loadpointFilter"
-											class="custom-select"
-											@change="changeLoadpointFilter"
-										>
-											<option
-												v-for="{
-													name,
-													value,
-													count,
-												} in loadpointFilterOptions"
-												:key="value"
-												:value="value"
-												:disabled="count === 0"
-											>
-												{{ name }} ({{ count }})
-											</option>
-										</select>
+									<CustomSelect
+										:selected="loadpointFilter"
+										:options="loadpointFilterOptions"
+										data-testid="filter-loadpoint"
+										@change="changeLoadpointFilter"
+									>
 										<span
-											class="fw-normal text-decoration-underline text-nowrap text-muted pe-none"
+											class="fw-normal text-decoration-underline text-nowrap text-gray pe-none"
 										>
 											{{ loadpointFilter || $t("sessions.filter.filter") }}
 										</span>
-									</label>
+									</CustomSelect>
 								</th>
 								<th
 									v-if="showVehicles"
 									scope="col"
-									class="align-top"
+									class="align-top d-none d-md-table-cell"
 									data-testid="vehicle"
 								>
 									{{ $t("sessions.vehicle") }}
-									<label class="position-relative d-block">
-										<select
-											:value="vehicleFilter"
-											class="custom-select"
-											@change="changeVehicleFilter"
-										>
-											<option
-												v-for="{
-													name,
-													value,
-													count,
-												} in vehicleFilterOptions"
-												:key="value"
-												:value="value"
-												:disabled="count === 0"
-											>
-												{{ name }} ({{ count }})
-											</option>
-										</select>
+									<CustomSelect
+										:selected="vehicleFilter"
+										:options="vehicleFilterOptions"
+										data-testid="filter-vehicle"
+										@change="changeVehicleFilter"
+									>
 										<span
-											class="fw-normal text-decoration-underline text-nowrap text-muted pe-none"
+											class="fw-normal text-decoration-underline text-nowrap text-gray pe-none"
 										>
 											{{ vehicleFilter || $t("sessions.filter.filter") }}
 										</span>
-									</label>
+									</CustomSelect>
 								</th>
-								<th scope="col" class="align-top text-end">
-									{{ $t("sessions.energy") }}
-									<div class="text-muted fw-normal">
-										{{ fmtKWh(chargedEnergy * 1e3, chargedEnergy >= 1) }}
+								<th scope="col" class="align-top d-md-none text-truncate">
+									<div
+										v-if="showLoadpoints"
+										class="d-flex flex-wrap text-truncate"
+									>
+										<div class="me-2 text-truncate">
+											{{ $t("sessions.loadpoint") }}
+										</div>
+										<CustomSelect
+											:selected="loadpointFilter"
+											:options="loadpointFilterOptions"
+											data-testid="filter-loadpoint"
+											@change="changeLoadpointFilter"
+										>
+											<span
+												class="fw-normal text-decoration-underline text-nowrap text-gray pe-none text-truncate"
+											>
+												{{
+													loadpointFilter || $t("sessions.filter.filter")
+												}}
+											</span>
+										</CustomSelect>
+									</div>
+									<div
+										class="text-truncate"
+										:class="{ 'd-flex flex-wrap': showLoadpoints }"
+									>
+										<div class="me-2 text-truncate">
+											{{ $t("sessions.vehicle") }}
+										</div>
+										<CustomSelect
+											:selected="vehicleFilter"
+											:options="vehicleFilterOptions"
+											data-testid="filter-vehicle"
+											@change="changeVehicleFilter"
+										>
+											<span
+												class="fw-normal text-decoration-underline text-nowrap text-gray pe-none text-truncate"
+											>
+												{{ vehicleFilter || $t("sessions.filter.filter") }}
+											</span>
+										</CustomSelect>
 									</div>
 								</th>
 								<th
-									v-if="hasSolarPercentage"
+									v-for="column in columns"
+									:key="column.name"
 									scope="col"
-									class="align-top text-end"
+									:data-testid="`sessions-head-${column.name}`"
+									class="align-top text-end d-none d-md-table-cell"
 								>
-									{{ $t("sessions.solar") }}
-									<div
-										v-if="solarPercentage != null"
-										class="text-muted fw-normal"
+									{{ $t(`sessions.${column.name}`) }}<br />
+									<div class="text-gray fw-normal">{{ column.unit }}</div>
+								</th>
+								<th
+									scope="col"
+									class="align-top text-end d-md-none"
+									data-testid="sessions-head-mobile"
+								>
+									<span v-if="columns.length === 1">
+										{{ $t(`sessions.${mobileColumn.name}`) }}
+									</span>
+									<CustomSelect
+										v-else
+										:selected="selectedMobileColumn"
+										:options="mobileColumnOptions"
+										data-testid="mobile-column"
+										@change="changeMobileColumn"
 									>
-										{{ fmtNumber(solarPercentage, 1) }}%
-									</div>
+										<span class="text-decoration-underline">
+											{{ $t(`sessions.${mobileColumn.name}`) }}
+										</span>
+									</CustomSelect>
+									<div class="text-gray fw-normal">{{ mobileColumn.unit }}</div>
 								</th>
-								<th v-if="hasPrice" scope="col" class="align-top text-end">
-									{{ $t("sessions.price") }}
-									<div v-if="price != null" class="text-muted fw-normal">
-										{{ fmtMoney(price, currency) }}
-										{{ fmtCurrencySymbol(currency) }}
-									</div>
+							</tr>
+						</thead>
+						<tfoot class="sticky-bottom">
+							<tr data-testid="sessions-foot">
+								<th scope="col" class="align-top ps-0">
+									{{ $t("sessions.total") }}
 								</th>
-								<th v-if="hasPrice" scope="col" class="align-top text-end">
-									{{ $t("sessions.avgPrice") }}
-									<div v-if="pricePerKWh != null" class="text-muted fw-normal">
-										{{ fmtPricePerKWh(pricePerKWh, currency) }}
-									</div>
+								<th
+									v-if="showLoadpoints"
+									scope="col"
+									class="d-none d-md-table-cell"
+								></th>
+								<th
+									v-if="showVehicles"
+									scope="col"
+									class="d-none d-md-table-cell"
+								></th>
+								<th scope="col" class="d-md-none"></th>
+								<th
+									v-for="column in columns"
+									:key="column.name"
+									:data-testid="`sessions-foot-${column.name}`"
+									scope="col"
+									class="align-top text-end d-none d-md-table-cell"
+								>
+									{{ column.format(column.total) }}
 								</th>
 								<th
-									v-if="hasCo2"
 									scope="col"
-									class="align-top text-end pe-0"
-									data-testid="co2"
+									data-testid="sessions-foot-mobile"
+									class="align-top text-end d-md-none"
 								>
-									{{ $t("sessions.co2") }}
-									<div v-if="co2PerKWh != null" class="text-muted fw-normal">
-										{{ fmtCo2Medium(co2PerKWh) }}
-									</div>
+									{{ mobileColumn.format(mobileColumn.total) }}
 								</th>
 							</tr>
-						</thead>
+						</tfoot>
 						<tbody>
 							<tr
 								v-for="(session, id) in filteredSessions"
@@ -169,55 +210,47 @@
 								@click="showDetails(session.id)"
 							>
 								<td class="ps-0">
-									<u>{{ fmtFullDateTime(new Date(session.created), true) }}</u>
+									{{ fmtFullDateTime(new Date(session.created), true) }}
 								</td>
-								<td v-if="showLoadpoints">
+								<td v-if="showLoadpoints" class="d-none d-md-table-cell">
 									{{ session.loadpoint }}
 								</td>
-								<td v-if="showVehicles">
+								<td v-if="showVehicles" class="d-none d-md-table-cell">
 									{{ session.vehicle }}
 								</td>
-								<td class="text-end">
-									{{
-										fmtKWh(
-											session.chargedEnergy * 1e3,
-											session.chargedEnergy >= 1
-										)
-									}}
-								</td>
-								<td v-if="hasSolarPercentage" class="text-end">
-									<span v-if="session.solarPercentage != null">
-										{{ fmtNumber(session.solarPercentage, 1) }}%
-									</span>
-									<span v-else class="text-muted">-</span>
-								</td>
-								<td v-if="hasPrice" class="text-end">
-									<span v-if="session.price != null">
-										{{ fmtMoney(session.price, currency) }}
-										{{ fmtCurrencySymbol(currency) }}
-									</span>
-									<span v-else class="text-muted">-</span>
+								<td class="d-md-none text-truncate">
+									<div v-if="showLoadpoints">{{ session.loadpoint }}</div>
+									<div>{{ session.vehicle }}</div>
 								</td>
-								<td v-if="hasPrice" class="text-end">
-									<span v-if="session.pricePerKWh != null">
-										{{ fmtPricePerKWh(session.pricePerKWh, currency) }}
+								<td
+									v-for="column in columns"
+									:key="column.name"
+									class="text-end d-none d-md-table-cell"
+								>
+									<span v-if="column.value(session) === null" class="text-gray">
+										-
 									</span>
-									<span v-else class="text-muted">-</span>
+									<span v-else>{{ column.format(column.value(session)) }}</span>
 								</td>
-								<td v-if="hasCo2" class="text-end pe-0">
-									<span v-if="session.co2PerKWh != null">
-										{{ fmtCo2Medium(session.co2PerKWh) }}
+								<td class="text-end d-md-none">
+									<span
+										v-if="mobileColumn.value(session) === null"
+										class="text-gray"
+									>
+										-
 									</span>
-									<span v-else class="text-muted">-</span>
+									<span v-else>{{
+										mobileColumn.format(mobileColumn.value(session))
+									}}</span>
 								</td>
 							</tr>
 						</tbody>
 					</table>
 				</div>
-				<div class="d-grid gap-2 d-sm-block mt-3 mb-5">
+				<div class="d-grid gap-2 d-md-block mt-3 mb-5">
 					<a
 						v-if="currentSessions.length"
-						class="btn btn-outline-secondary text-nowrap me-sm-2"
+						class="btn btn-outline-secondary text-nowrap me-md-2"
 						:href="csvLink"
 						download
 					>
@@ -245,17 +278,18 @@
 <script>
 import Modal from "bootstrap/js/dist/modal";
 import TopNavigation from "../components/TopNavigation.vue";
-import "@h2d2/shopicons/es/bold/arrowback";
+import "@h2d2/shopicons/es/regular/home";
 import "@h2d2/shopicons/es/regular/angledoubleleftsmall";
 import "@h2d2/shopicons/es/regular/angledoublerightsmall";
 import formatter from "../mixins/formatter";
 import api from "../api";
 import store from "../store";
+import CustomSelect from "../components/CustomSelect.vue";
 import ChargingSessionModal from "../components/ChargingSessionModal.vue";
 
 export default {
 	name: "ChargingSessions",
-	components: { TopNavigation, ChargingSessionModal },
+	components: { TopNavigation, ChargingSessionModal, CustomSelect },
 	mixins: [formatter],
 	props: {
 		notifications: Array,
@@ -268,6 +302,7 @@ export default {
 		return {
 			sessions: [],
 			selectedSessionId: undefined,
+			selectedMobileColumn: undefined,
 		};
 	},
 	computed: {
@@ -286,6 +321,62 @@ export default {
 		filteredSessions() {
 			return this.currentSessions.filter(this.filterByLoadpoint).filter(this.filterByVehicle);
 		},
+		columns() {
+			const columns = [
+				{
+					name: "energy",
+					unit: "kWh",
+					total: this.chargedEnergy,
+					value: (session) => session.chargedEnergy,
+					format: (value) => this.fmtKWh(value * 1e3, true, false),
+				},
+				{
+					name: "solar",
+					unit: "%",
+					total: this.solarPercentage,
+					value: (session) => session.solarPercentage,
+					format: (value) => this.fmtNumber(value, 1),
+				},
+				{
+					name: "price",
+					unit: this.fmtCurrencySymbol(this.currency),
+					total: this.price,
+					value: (session) => session.price,
+					format: (value) => this.fmtMoney(value, this.currency),
+				},
+				{
+					name: "avgPrice",
+					unit: this.pricePerKWhUnit(this.currency),
+					total: this.pricePerKWh,
+					value: (session) => session.pricePerKWh,
+					format: (value) => this.fmtPricePerKWh(value, this.currency, false, false),
+				},
+				{
+					name: "co2",
+					unit: "g/kWh",
+					total: this.co2PerKWh,
+					value: (session) => session.co2PerKWh,
+					format: (value) => this.fmtNumber(value, 0),
+				},
+			];
+			// only columns with values are shown
+			return columns.filter((column) => {
+				if (column.name === "energy") return true;
+				return this.currentSessions.some((s) => column.value(s));
+			});
+		},
+		mobileColumn() {
+			const column = this.columns.find((column) => column.name === this.selectedMobileColumn);
+			return column || this.columns[0];
+		},
+		mobileColumnOptions() {
+			return this.columns.map((column) => {
+				return {
+					name: this.$t(`sessions.${column.name}`),
+					value: column.name,
+				};
+			});
+		},
 		vehicleFilterOptions() {
 			const options = [
 				{
@@ -320,15 +411,6 @@ export default {
 		price() {
 			return this.filteredSessions.reduce((total, s) => total + s.price, 0);
 		},
-		hasPrice() {
-			return this.filteredSessions.find((s) => s.price != null) != null;
-		},
-		hasSolarPercentage() {
-			return this.filteredSessions.find((s) => s.solarPercentage != null) != null;
-		},
-		hasCo2() {
-			return this.filteredSessions.find((s) => s.co2PerKWh != null) != null;
-		},
 		showVehicles() {
 			return this.hasMultipleVehicles || this.vehicleFilter;
 		},
@@ -344,21 +426,42 @@ export default {
 			return new Set(loadpoints).size > 1;
 		},
 		pricePerKWh() {
-			return this.price / this.chargedEnergy;
+			const total = this.filteredSessions
+				.filter((s) => s.price !== null)
+				.reduce(
+					(total, s) => ({
+						price: total.price + s.price,
+						chargedEnergy: total.chargedEnergy + s.chargedEnergy,
+					}),
+					{ price: 0, chargedEnergy: 0 }
+				);
+			return total.price / total.chargedEnergy;
 		},
 		co2PerKWh() {
-			const emittedCo2 = this.filteredSessions.reduce(
-				(total, s) => total + s.chargedEnergy * s.co2PerKWh,
-				0
-			);
-			return emittedCo2 / this.chargedEnergy;
+			const total = this.filteredSessions
+				.filter((s) => s.co2PerKWh !== null)
+				.reduce(
+					(total, s) => ({
+						emittedCo2: total.emittedCo2 + s.chargedEnergy * s.co2PerKWh,
+						chargedEnergy: total.chargedEnergy + s.chargedEnergy,
+					}),
+					{ emittedCo2: 0, chargedEnergy: 0 }
+				);
+			return total.emittedCo2 / total.chargedEnergy;
 		},
 		solarPercentage() {
-			const chargedSolarEnergy = this.filteredSessions.reduce(
-				(total, s) => total + s.chargedEnergy * (s.solarPercentage / 100),
-				0
-			);
-			return (100 / this.chargedEnergy) * chargedSolarEnergy;
+			const total = this.filteredSessions
+				.filter((s) => s.solarPercentage !== null)
+				.reduce(
+					(total, s) => ({
+						chargedSolarEnergy:
+							total.chargedSolarEnergy + s.chargedEnergy * (s.solarPercentage / 100),
+						chargedEnergy: total.chargedEnergy + s.chargedEnergy,
+					}),
+					{ chargedSolarEnergy: 0, chargedEnergy: 0 }
+				);
+
+			return (100 / total.chargedEnergy) * total.chargedSolarEnergy;
 		},
 		loadpoints() {
 			return [...new Set(this.currentSessions.map((s) => s.loadpoint))];
@@ -454,6 +557,9 @@ export default {
 				.filter(this.filterByVehicle)
 				.filter((s) => !loadpoint || s.loadpoint === loadpoint).length;
 		},
+		changeMobileColumn(event) {
+			this.selectedMobileColumn = event.target.value;
+		},
 		changeLoadpointFilter(event) {
 			const loadpoint = event.target.value || undefined;
 			this.$router.push({ query: { ...this.$route.query, loadpoint } });
@@ -486,11 +592,11 @@ export default {
 };
 </script>
 <style scoped>
-.back {
-	width: 22px;
+.home {
 	height: 22px;
+	width: 22px;
 	position: relative;
-	top: -2px;
+	top: -3px;
 }
 .custom-select {
 	left: 0;
@@ -501,4 +607,43 @@ export default {
 	opacity: 0;
 	-webkit-appearance: menulist-button;
 }
+.table {
+	border-collapse: separate;
+	border-spacing: 0;
+}
+.table thead,
+.table tfoot {
+	background: var(--evcc-background);
+}
+.table tfoot th {
+	border-top-width: 2px;
+}
+.table thead th {
+	border-bottom-width: 2px;
+}
+.table tbody tr:last-child td {
+	border-bottom-width: 0;
+}
+
+.sticky-top,
+.sticky-bottom {
+	z-index: 1;
+}
+@media (max-width: 576px) {
+	.table td,
+	.table th {
+		width: 50%;
+	}
+	.table td:first-child,
+	.table th:first-child,
+	.table td:last-child,
+	.table th:last-child {
+		width: 25%;
+	}
+
+	.table td.text-truncate,
+	.table th.text-truncate {
+		max-width: 1px;
+	}
+}
 </style>
diff --git a/charger/easee.go b/charger/easee.go
index e0a766b6..580c8dd4 100644
--- a/charger/easee.go
+++ b/charger/easee.go
@@ -178,17 +178,17 @@ func NewEasee(user, password, charger string, timeout time.Duration) (*Easee, er
 	}
 
 	if err == nil {
-		go c.heartbeat()
+		go c.refresh()
 	}
 
 	return c, err
 }
 
-// heartbeat ensures tokens are refreshed even when not charging for longer time
-func (c *Easee) heartbeat() {
-	for range time.Tick(6 * time.Hour) {
-		if _, err := c.chargerSite(c.charger); err != nil {
-			c.log.ERROR.Println("heartbeat:", err)
+// refresh ensures tokens are refreshed even when not charging for longer time
+func (c *Easee) refresh() {
+	for range time.Tick(5 * time.Minute) {
+		if _, err := c.Client.Transport.(*oauth2.Transport).Source.Token(); err != nil {
+			c.log.ERROR.Println("token refresh:", err)
 		}
 	}
 }
@@ -399,7 +399,6 @@ func (c *Easee) Enable(enable bool) error {
 		targetCurrent = 32
 	}
 
-	c.log.DEBUG.Printf("send command: %s", action)
 	uri := fmt.Sprintf("%s/chargers/%s/commands/%s", easee.API, c.charger, action)
 	if err := c.postJSONAndWait(uri, nil); err != nil {
 		return err
@@ -409,13 +408,7 @@ func (c *Easee) Enable(enable bool) error {
 		return err
 	}
 
-	c.mux.Lock()
-	dynamicChargerCurrent := c.dynamicChargerCurrent
-	c.mux.Unlock()
-
-	c.log.DEBUG.Printf("DCC update received, current: %.3f, dynamicChargerCurrent: %.3f", c.current, dynamicChargerCurrent)
 	if enable {
-		c.log.DEBUG.Printf("send enable, reset current: %.3f", c.current)
 		// reset currents after enable, as easee automatically resets to maxA
 		return c.MaxCurrent(int64(c.current))
 	}
@@ -465,7 +458,6 @@ func (c *Easee) postJSONAndWait(uri string, data any) error {
 }
 
 func (c *Easee) waitForTickResponse(expectedTick int64) error {
-	c.log.TRACE.Printf("wait for tick response: %d", expectedTick)
 	for {
 		select {
 		case cmdResp := <-c.cmdC:
@@ -473,11 +465,9 @@ func (c *Easee) waitForTickResponse(expectedTick int64) error {
 				if !cmdResp.WasAccepted {
 					return fmt.Errorf("command rejected: %d", cmdResp.Ticks)
 				}
-				c.log.TRACE.Printf("received tick response: %d", cmdResp.Ticks)
 				return nil
 			}
 		case <-time.After(10 * time.Second):
-			c.log.TRACE.Printf("tick response timed out: %d", expectedTick)
 			return api.ErrTimeout
 		}
 	}
@@ -485,8 +475,6 @@ func (c *Easee) waitForTickResponse(expectedTick int64) error {
 
 // wait for up to 3s for current become targetCurrent
 func (c *Easee) waitForDynamicChargerCurrent(targetCurrent float64) error {
-	c.log.DEBUG.Printf("wait for DCC update: %.3f", targetCurrent)
-
 	// check any updates received meanwhile
 	c.mux.Lock()
 	if c.dynamicChargerCurrent == targetCurrent {
@@ -506,7 +494,6 @@ func (c *Easee) waitForDynamicChargerCurrent(targetCurrent float64) error {
 			if err != nil {
 				continue
 			}
-			c.log.TRACE.Printf("DCC update received: %.3f (want: %.3f)", value.(float64), targetCurrent)
 			if value.(float64) == targetCurrent {
 				return nil
 			}
@@ -670,8 +657,7 @@ func (c *Easee) updateSmartCharging() {
 
 		uri := fmt.Sprintf("%s/chargers/%s/settings", easee.API, c.charger)
 
-		err := c.postJSONAndWait(uri, data)
-		if err != nil {
+		if err := c.postJSONAndWait(uri, data); err != nil {
 			c.log.WARN.Printf("smart charging: %v", err)
 			return
 		}
diff --git a/charger/easee/identity.go b/charger/easee/identity.go
index aebbb317..0c24b5e3 100644
--- a/charger/easee/identity.go
+++ b/charger/easee/identity.go
@@ -35,7 +35,6 @@ func (t *Token) AsOAuth2Token() *oauth2.Token {
 // tokenSource is an oauth2.TokenSource
 type tokenSource struct {
 	*request.Helper
-	oauth2.TokenSource
 }
 
 // TokenSource creates an Easee token source
@@ -54,36 +53,40 @@ func TokenSource(log *util.Logger, user, password string) (oauth2.TokenSource, e
 
 	uri := fmt.Sprintf("%s/%s", API, "accounts/login")
 	req, err := request.New(http.MethodPost, uri, request.MarshalJSON(data), request.JSONEncoding)
+	if err != nil {
+		return nil, err
+	}
 
-	if err == nil {
-		var token Token
-		if err = c.DoJSON(req, &token); err == nil {
-			c.TokenSource = oauth.RefreshTokenSource(token.AsOAuth2Token(), c)
-		}
+	var token Token
+	if err := c.DoJSON(req, &token); err != nil {
+		return nil, err
 	}
 
-	return c, err
+	oauthToken := token.AsOAuth2Token()
+	ts := oauth2.ReuseTokenSourceWithExpiry(oauthToken, oauth.RefreshTokenSource(oauthToken, c), 15*time.Minute)
+
+	return ts, nil
 }
 
-func (c *tokenSource) RefreshToken(token *oauth2.Token) (*oauth2.Token, error) {
+func (c *tokenSource) RefreshToken(oauthToken *oauth2.Token) (*oauth2.Token, error) {
 	data := struct {
 		AccessToken  string `json:"accessToken"`
 		RefreshToken string `json:"refreshToken"`
 	}{
-		AccessToken:  token.AccessToken,
-		RefreshToken: token.RefreshToken,
+		AccessToken:  oauthToken.AccessToken,
+		RefreshToken: oauthToken.RefreshToken,
 	}
 
 	uri := fmt.Sprintf("%s/%s", API, "accounts/refresh_token")
 	req, err := request.New(http.MethodPost, uri, request.MarshalJSON(data), request.JSONEncoding)
+	if err != nil {
+		return oauthToken, err
+	}
 
-	var res *oauth2.Token
-	if err == nil {
-		var refreshed Token
-		if err = c.DoJSON(req, &refreshed); err == nil {
-			res = refreshed.AsOAuth2Token()
-		}
+	var token Token
+	if err := c.DoJSON(req, &token); err != nil {
+		return oauthToken, err
 	}
 
-	return res, err
+	return token.AsOAuth2Token(), nil
 }
diff --git a/charger/keba-modbus.go b/charger/keba-modbus.go
index 36ef1b6d..1e4c4bbf 100644
--- a/charger/keba-modbus.go
+++ b/charger/keba-modbus.go
@@ -168,10 +168,10 @@ func (wb *Keba) Status() (api.ChargeStatus, error) {
 	}
 
 	switch status := binary.BigEndian.Uint32(b); status {
-	case 0, 3:
+	case 0, 1, 3:
 		return api.StatusA, nil
 
-	case 1, 5:
+	case 5:
 		return api.StatusB, nil
 
 	case 7:
diff --git a/charger/ocpp/instance.go b/charger/ocpp/instance.go
index 792d9a8f..73da687c 100644
--- a/charger/ocpp/instance.go
+++ b/charger/ocpp/instance.go
@@ -6,6 +6,12 @@ import (
 
 	"github.com/evcc-io/evcc/util"
 	ocpp16 "github.com/lorenzodonini/ocpp-go/ocpp1.6"
+	"github.com/lorenzodonini/ocpp-go/ocpp1.6/core"
+	"github.com/lorenzodonini/ocpp-go/ocpp1.6/firmware"
+	"github.com/lorenzodonini/ocpp-go/ocpp1.6/localauth"
+	"github.com/lorenzodonini/ocpp-go/ocpp1.6/remotetrigger"
+	"github.com/lorenzodonini/ocpp-go/ocpp1.6/reservation"
+	"github.com/lorenzodonini/ocpp-go/ocpp1.6/smartcharging"
 	"github.com/lorenzodonini/ocpp-go/ocppj"
 	"github.com/lorenzodonini/ocpp-go/ws"
 )
@@ -23,7 +29,12 @@ func Instance() *CS {
 		server := ws.NewServer()
 		server.SetTimeoutConfig(timeoutConfig)
 
-		cs := ocpp16.NewCentralSystem(nil, server)
+		dispatcher := ocppj.NewDefaultServerDispatcher(ocppj.NewFIFOQueueMap(0))
+		dispatcher.SetTimeout(time.Minute)
+
+		endpoint := ocppj.NewServer(server, dispatcher, nil, core.Profile, localauth.Profile, firmware.Profile, reservation.Profile, remotetrigger.Profile, smartcharging.Profile)
+
+		cs := ocpp16.NewCentralSystem(endpoint, server)
 
 		instance = &CS{
 			log:           util.NewLogger("ocpp"),
diff --git a/cmd/configure/configure.go b/cmd/configure/configure.go
index ebb05c89..8dfe61f4 100644
--- a/cmd/configure/configure.go
+++ b/cmd/configure/configure.go
@@ -6,7 +6,6 @@ import (
 	"text/template"
 
 	"github.com/Masterminds/sprig/v3"
-	"github.com/evcc-io/evcc/util/machine"
 	"github.com/evcc-io/evcc/util/templates"
 )
 
@@ -123,10 +122,6 @@ func (c *Configure) RenderConfiguration() ([]byte, error) {
 		panic(err)
 	}
 
-	// Assign random plant id. Don't use actual machine-id as file might
-	// be copied around to a different machine.
-	c.config.Plant = machine.RandomID()
-
 	out := new(bytes.Buffer)
 	err = tmpl.Execute(out, c.config)
 
diff --git a/cmd/configure/main.go b/cmd/configure/main.go
index cbf3a515..db89423c 100644
--- a/cmd/configure/main.go
+++ b/cmd/configure/main.go
@@ -14,6 +14,7 @@ import (
 	"github.com/evcc-io/evcc/hems/semp"
 	"github.com/evcc-io/evcc/server"
 	"github.com/evcc-io/evcc/util"
+	"github.com/evcc-io/evcc/util/machine"
 	"github.com/evcc-io/evcc/util/templates"
 	"github.com/nicksnyder/go-i18n/v2/i18n"
 	"golang.org/x/exp/maps"
@@ -70,6 +71,14 @@ func (c *CmdConfigure) Run(log *util.Logger, flagLang string, advancedMode, expa
 
 	c.setDefaultTexts()
 
+	// Assign random plant id. Don't use actual machine-id as file might
+	// be copied around to a different machine.
+	c.configuration.config.Plant = machine.RandomID()
+
+	if err = machine.CustomID(c.configuration.config.Plant); err != nil {
+		panic(err)
+	}
+
 	fmt.Println()
 	fmt.Println(c.localizedString("Intro"))
 
diff --git a/core/loadpoint.go b/core/loadpoint.go
index 1016ebb6..b9ee3080 100644
--- a/core/loadpoint.go
+++ b/core/loadpoint.go
@@ -50,7 +50,7 @@ const (
 	timerInactive = "inactive"
 
 	minActiveCurrent = 1.0 // minimum current at which a phase is treated as active
-	minActiveVoltage = 208 // minimum voltage at which a phase is treated as active
+	minActiveVoltage = 207 // minimum voltage at which a phase is treated as active
 
 	guardGracePeriod          = 60 * time.Second // allow out of sync during this timespan
 	phaseSwitchCommandTimeout = 30 * time.Second // do not sync charger enabled/disabled state during this timespan
@@ -315,7 +315,7 @@ func (lp *Loadpoint) collectDefaults() {
 		*actionCfg.MaxCurrent = lp.GetMaxCurrent()
 		*actionCfg.MinSoc = lp.GetMinSoc()
 		*actionCfg.TargetSoc = lp.GetTargetSoc()
-		*actionCfg.Priority = lp.Priority()
+		*actionCfg.Priority = lp.GetPriority()
 	} else {
 		lp.log.ERROR.Printf("error allocating action config: %v", err)
 	}
@@ -557,6 +557,9 @@ func (lp *Loadpoint) applyAction(actionCfg api.ActionConfig) {
 	if actionCfg.TargetSoc != nil {
 		lp.SetTargetSoc(*actionCfg.TargetSoc)
 	}
+	if actionCfg.Priority != nil {
+		lp.SetPriority(*actionCfg.Priority)
+	}
 }
 
 // Prepare loadpoint configuration by adding missing helper elements
@@ -603,6 +606,7 @@ func (lp *Loadpoint) Prepare(uiChan chan<- util.Param, pushChan chan<- push.Even
 	}
 
 	lp.publish("mode", lp.GetMode())
+	lp.publish("priority", lp.GetPriority())
 	lp.publish(targetSoc, lp.GetTargetSoc())
 	lp.publish(minSoc, lp.GetMinSoc())
 
@@ -1261,15 +1265,15 @@ func (lp *Loadpoint) updateChargeVoltages() {
 	lp.publish("chargeVoltages", chargeVoltages)
 
 	// Quine-McCluskey for (¬L1∧L2∧¬L3) ∨ (L1∧L2∧¬L3) ∨ (¬L1∧¬L2∧L3) ∨ (L1∧¬L2∧L3) ∨ (¬L1∧L2∧L3) -> ¬L1 ∧ L3 ∨ L2 ∧ ¬L3 ∨ ¬L2 ∧ L3
-	if !(u1 > minActiveVoltage) && (u3 > minActiveVoltage) || (u2 > minActiveVoltage) && !(u3 > minActiveVoltage) || !(u2 > minActiveVoltage) && (u3 > minActiveVoltage) {
+	if !(u1 >= minActiveVoltage) && (u3 >= minActiveVoltage) || (u2 >= minActiveVoltage) && !(u3 >= minActiveVoltage) || !(u2 >= minActiveVoltage) && (u3 >= minActiveVoltage) {
 		lp.log.WARN.Printf("invalid phase wiring between charge meter and charger")
 	}
 
 	var phases int
-	if (u1 > minActiveVoltage) || (u2 > minActiveVoltage) || (u3 > minActiveVoltage) {
+	if (u1 >= minActiveVoltage) || (u2 >= minActiveVoltage) || (u3 >= minActiveVoltage) {
 		phases = 3
 	}
-	if (u1 > minActiveVoltage) && (u2 < minActiveVoltage) && (u3 < minActiveVoltage) {
+	if (u1 >= minActiveVoltage) && (u2 < minActiveVoltage) && (u3 < minActiveVoltage) {
 		phases = 1
 	}
 
diff --git a/core/loadpoint/api.go b/core/loadpoint/api.go
index 1e2a33b9..226f505b 100644
--- a/core/loadpoint/api.go
+++ b/core/loadpoint/api.go
@@ -17,7 +17,6 @@ type Controller interface {
 type API interface {
 	// Title returns the defined loadpoint title
 	Title() string
-	Priority() int
 
 	//
 	// status
@@ -30,6 +29,11 @@ type API interface {
 	// settings
 	//
 
+	// GetPriority returns the loadpoint priority
+	GetPriority() int
+	// SetPriority sets the loadpoint priority
+	SetPriority(int)
+
 	// GetMode returns the charge mode
 	GetMode() api.ChargeMode
 	// SetMode sets the charge mode
diff --git a/core/loadpoint/mock.go b/core/loadpoint/mock.go
index 5bc56870..a2c2addb 100644
--- a/core/loadpoint/mock.go
+++ b/core/loadpoint/mock.go
@@ -205,6 +205,20 @@ func (mr *MockAPIMockRecorder) GetPlan(arg0, arg1 interface{}) *gomock.Call {
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPlan", reflect.TypeOf((*MockAPI)(nil).GetPlan), arg0, arg1)
 }
 
+// GetPriority mocks base method.
+func (m *MockAPI) GetPriority() int {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetPriority")
+	ret0, _ := ret[0].(int)
+	return ret0
+}
+
+// GetPriority indicates an expected call of GetPriority.
+func (mr *MockAPIMockRecorder) GetPriority() *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPriority", reflect.TypeOf((*MockAPI)(nil).GetPriority))
+}
+
 // GetRemainingDuration mocks base method.
 func (m *MockAPI) GetRemainingDuration() time.Duration {
 	m.ctrl.T.Helper()
@@ -317,20 +331,6 @@ func (mr *MockAPIMockRecorder) HasChargeMeter() *gomock.Call {
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasChargeMeter", reflect.TypeOf((*MockAPI)(nil).HasChargeMeter))
 }
 
-// Priority mocks base method.
-func (m *MockAPI) Priority() int {
-	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "Priority")
-	ret0, _ := ret[0].(int)
-	return ret0
-}
-
-// Priority indicates an expected call of Priority.
-func (mr *MockAPIMockRecorder) Priority() *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Priority", reflect.TypeOf((*MockAPI)(nil).Priority))
-}
-
 // RemoteControl mocks base method.
 func (m *MockAPI) RemoteControl(arg0 string, arg1 RemoteDemand) {
 	m.ctrl.T.Helper()
@@ -429,6 +429,18 @@ func (mr *MockAPIMockRecorder) SetPhases(arg0 interface{}) *gomock.Call {
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetPhases", reflect.TypeOf((*MockAPI)(nil).SetPhases), arg0)
 }
 
+// SetPriority mocks base method.
+func (m *MockAPI) SetPriority(arg0 int) {
+	m.ctrl.T.Helper()
+	m.ctrl.Call(m, "SetPriority", arg0)
+}
+
+// SetPriority indicates an expected call of SetPriority.
+func (mr *MockAPIMockRecorder) SetPriority(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetPriority", reflect.TypeOf((*MockAPI)(nil).SetPriority), arg0)
+}
+
 // SetTargetEnergy mocks base method.
 func (m *MockAPI) SetTargetEnergy(arg0 float64) {
 	m.ctrl.T.Helper()
diff --git a/core/loadpoint_api.go b/core/loadpoint_api.go
index d313b530..07552264 100644
--- a/core/loadpoint_api.go
+++ b/core/loadpoint_api.go
@@ -18,11 +18,6 @@ func (lp *Loadpoint) Title() string {
 	return lp.Title_
 }
 
-// Priority returns the loadpoint priority
-func (lp *Loadpoint) Priority() int {
-	return lp.Priority_
-}
-
 // GetStatus returns the charging status
 func (lp *Loadpoint) GetStatus() api.ChargeStatus {
 	lp.Lock()
@@ -102,6 +97,26 @@ func (lp *Loadpoint) SetTargetEnergy(energy float64) {
 	}
 }
 
+// GetPriority returns the loadpoint priority
+func (lp *Loadpoint) GetPriority() int {
+	lp.Lock()
+	defer lp.Unlock()
+	return lp.Priority_
+}
+
+// SetPriority sets the loadpoint priority
+func (lp *Loadpoint) SetPriority(prio int) {
+	lp.Lock()
+	defer lp.Unlock()
+
+	lp.log.DEBUG.Println("set priority:", prio)
+
+	if lp.Priority_ != prio {
+		lp.Priority_ = prio
+		lp.publish("priority", prio)
+	}
+}
+
 // GetTargetSoc returns loadpoint charge target soc
 func (lp *Loadpoint) GetTargetSoc() int {
 	lp.Lock()
diff --git a/core/prioritizer/prioritizer.go b/core/prioritizer/prioritizer.go
index 334dac20..346bcfe7 100644
--- a/core/prioritizer/prioritizer.go
+++ b/core/prioritizer/prioritizer.go
@@ -1,15 +1,20 @@
 package prioritizer
 
 import (
+	"fmt"
+
 	"github.com/evcc-io/evcc/core/loadpoint"
+	"github.com/evcc-io/evcc/util"
 )
 
 type Prioritizer struct {
+	log    *util.Logger
 	demand map[loadpoint.API]float64
 }
 
-func New() *Prioritizer {
+func New(log *util.Logger) *Prioritizer {
 	return &Prioritizer{
+		log:    log,
 		demand: make(map[loadpoint.API]float64),
 	}
 }
@@ -21,14 +26,23 @@ func (p *Prioritizer) UpdateChargePowerFlexibility(lp loadpoint.API) {
 }
 
 func (p *Prioritizer) GetChargePowerFlexibility(lp loadpoint.API) float64 {
-	prio := lp.Priority()
+	prio := lp.GetPriority()
+
+	var (
+		reduceBy float64
+		msg      string
+	)
 
-	var reduceBy float64
 	for lp, power := range p.demand {
-		if lp.Priority() < prio {
+		if lp.GetPriority() < prio {
 			reduceBy += power
+			msg += fmt.Sprintf("%.0fW from %s at prio %d, ", power, lp.Title(), lp.GetPriority())
 		}
 	}
 
+	if p.log != nil && reduceBy > 0 {
+		p.log.DEBUG.Printf("lp %s at prio %d gets additional %stotal %.0fW\n", lp.Title(), lp.GetPriority(), msg, reduceBy)
+	}
+
 	return reduceBy
 }
diff --git a/core/site.go b/core/site.go
index 5dc2349b..390d82b1 100644
--- a/core/site.go
+++ b/core/site.go
@@ -113,7 +113,7 @@ func NewSiteFromConfig(
 	site.loadpoints = loadpoints
 	site.tariffs = tariffs
 	site.coordinator = coordinator.New(log, vehicles)
-	site.prioritizer = prioritizer.New()
+	site.prioritizer = prioritizer.New(log)
 	site.savings = NewSavings(tariffs)
 
 	site.restoreSettings()
@@ -715,6 +715,8 @@ func (site *Site) update(lp Updater) {
 		site.publish("homePower", homePower)
 
 		site.Health.Update()
+	} else {
+		site.log.ERROR.Println(err)
 	}
 
 	site.publishTariffs()
diff --git a/core/site_api.go b/core/site_api.go
index c906c9f7..f7d4ba7e 100644
--- a/core/site_api.go
+++ b/core/site_api.go
@@ -145,22 +145,18 @@ func (site *Site) GetTariff(tariff string) api.Tariff {
 		switch {
 		case site.tariffs.Planner != nil:
 			// prio 0: manually set planner tariff
-			site.log.DEBUG.Printf("planner tariff")
 			return site.tariffs.Planner
 
 		case site.tariffs.Grid != nil && site.tariffs.Grid.Type() == api.TariffTypePriceDynamic:
 			// prio 1: dynamic grid tariff
-			site.log.DEBUG.Printf("dynamic grid tariff")
 			return site.tariffs.Grid
 
 		case site.tariffs.Co2 != nil:
 			// prio 2: co2 tariff
-			site.log.DEBUG.Printf("co2 tariff")
 			return site.tariffs.Co2
 
 		default:
 			// prio 3: static grid tariff
-			site.log.DEBUG.Printf("static grid tariff")
 			return site.tariffs.Grid
 		}
 
diff --git a/evcc.dist.yaml b/evcc.dist.yaml
index 03151fc0..68e00d94 100644
--- a/evcc.dist.yaml
+++ b/evcc.dist.yaml
@@ -175,6 +175,10 @@ tariffs:
     # charges: # optional, additional charges per kWh
     # tax: # optional, additional tax (0.1 for 10%)
 
+    # type: octopusenergy
+    # tariff: AGILE-FLEX-22-11-25 # Tariff code
+    # region: A # optional
+
     # type: elering # Nordpool
     # region: ee # or lt, lv, fi
     # charges: # optional, additional charges per kWh
@@ -188,6 +192,10 @@ tariffs:
     # rate for feeding excess (pv) energy to the grid
     type: fixed
     price: 0.08 # EUR/kWh
+
+    # type: octopusenergy
+    # tariff: AGILE-FLEX-22-11-25 # Tariff code
+    # region: A # optional
   co2:
     # co2 tariff provides co2 intensity forecast and is for co2-optimized target charging if no variable grid tariff is specified
     # type: grünstromindex # GrünStromIndex (Germany only)
diff --git a/go.mod b/go.mod
index df3ae515..7451e5dc 100644
--- a/go.mod
+++ b/go.mod
@@ -67,7 +67,7 @@ require (
 	github.com/mitchellh/go-homedir v1.1.0
 	github.com/mitchellh/mapstructure v1.5.0
 	github.com/mlnoga/rct v0.1.2-0.20230227143934-71af1fb7dfa1
-	github.com/muka/go-bluetooth v0.0.0-20220219050759-674a63b8741a
+	github.com/muka/go-bluetooth v0.0.0-20221213043340-85dc80edc4e1
 	github.com/mxschmitt/golang-combinations v1.1.0
 	github.com/nicksnyder/go-i18n/v2 v2.2.1
 	github.com/nirasan/go-oauth-pkce-code-verifier v0.0.0-20220510032225-4f9f17eaec4c
@@ -209,3 +209,5 @@ require (
 )
 
 replace github.com/foogod/go-powerwall => github.com/andig/go-powerwall v0.2.1-0.20230525091927-777196024a18
+
+replace github.com/grid-x/modbus => github.com/evcc-io/modbus v0.0.0-20230705160100-23e1df2c01c3
diff --git a/go.sum b/go.sum
index 6672bf32..f6b28bcd 100644
--- a/go.sum
+++ b/go.sum
@@ -361,6 +361,8 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.m
 github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
 github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=
 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
+github.com/evcc-io/modbus v0.0.0-20230705160100-23e1df2c01c3 h1:QBtLqy85cy9KLRWqKxX+42bN1rWti8iU7pL7Vwf2UbE=
+github.com/evcc-io/modbus v0.0.0-20230705160100-23e1df2c01c3/go.mod h1:qVX2WhsI5xyAoM6I/MV1bXSKBPdLAjp7pCvieO/S0AY=
 github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc=
 github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
 github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
@@ -586,9 +588,6 @@ github.com/graph-gophers/graphql-transport-ws v0.0.2 h1:DbmSkbIGzj8SvHei6n8Mh9eL
 github.com/graph-gophers/graphql-transport-ws v0.0.2/go.mod h1:5BVKvFzOd2BalVIBFfnfmHjpJi/MZ5rOj8G55mXvZ8g=
 github.com/gregdel/pushover v1.2.0 h1:SLnpvJijUyEZvkJNyrldGhFhryYgQYlThSLpB5Oqt5k=
 github.com/gregdel/pushover v1.2.0/go.mod h1:EcaO66Nn1StkpEm1iKtBTV3d2A16SoMsVER1PthX7to=
-github.com/grid-x/modbus v0.0.0-20210714071042-7af2b65ec03b/go.mod h1:YaK0rKJenZ74vZFcSSLlAQqtG74PMI68eDjpDCDDmTw=
-github.com/grid-x/modbus v0.0.0-20230511111420-e90d491dbd4f h1:kYCn76Qlwsjbw3Bh4kF6ljySsK+uQODzGi3HUIZ+TpU=
-github.com/grid-x/modbus v0.0.0-20230511111420-e90d491dbd4f/go.mod h1:qVX2WhsI5xyAoM6I/MV1bXSKBPdLAjp7pCvieO/S0AY=
 github.com/grid-x/serial v0.0.0-20191104121038-e24bc9bf6f08/go.mod h1:kdOd86/VGFWRrtkNwf1MPk0u1gIjc4Y7R2j7nhwc7Rk=
 github.com/grid-x/serial v0.0.0-20211107191517-583c7356b3aa h1:Rsn6ARgNkXrsXJIzhkE4vQr5Gbx2LvtEMv4BJOK4LyU=
 github.com/grid-x/serial v0.0.0-20211107191517-583c7356b3aa/go.mod h1:kdOd86/VGFWRrtkNwf1MPk0u1gIjc4Y7R2j7nhwc7Rk=
@@ -832,8 +831,8 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN
 github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
 github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
-github.com/muka/go-bluetooth v0.0.0-20220219050759-674a63b8741a h1:fnzS9RRQW8B5AgNCxkN0vJ/AoX+Xfqk3sAYon3iVrzA=
-github.com/muka/go-bluetooth v0.0.0-20220219050759-674a63b8741a/go.mod h1:dMCjicU6vRBk34dqOmIZm0aod6gUwZXOXzBROqGous0=
+github.com/muka/go-bluetooth v0.0.0-20221213043340-85dc80edc4e1 h1:BuVRHr4HHJbk1DHyWkArJ7E8J/VA8ncCr/VLnQFazBo=
+github.com/muka/go-bluetooth v0.0.0-20221213043340-85dc80edc4e1/go.mod h1:dMCjicU6vRBk34dqOmIZm0aod6gUwZXOXzBROqGous0=
 github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
 github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU=
 github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
diff --git a/i18n/ar.toml b/i18n/ar.toml
new file mode 100644
index 00000000..6688ac2d
--- /dev/null
+++ b/i18n/ar.toml
@@ -0,0 +1,36 @@
+[batterySettings]
+batteryLevel = "مستوى البطارية"
+legendBottomName = "أولوية المنزل"
+legendBottomSubline = "لا تستخدم للشحن"
+legendMiddleName = "السيارة أولا"
+legendMiddleSubline = "المنزل الثاني"
+legendTitle = "كيف يجب استخدام الطاقة الشمسية?"
+legendTopAutostart = "يبدأ تلقائيًا"
+legendTopName = "بطارية تدعم الشحن"
+legendTopSubline = "بدون انقطاع"
+modalTitle = "إعدادات البطارية"
+note = "ملحوظة: تؤثر هذه الإعدادات فقط على الوضع الشمسي. يتم تعديل سلوك الشحن وفقًا لذلك."
+
+[batterySettings.bufferStart]
+full = "عندما أوشكت على الامتلاء"
+low = "عندما تكون ممتلئة إلى حد ما"
+medium = "عندما نصف ممتلئ"
+never = "فقط مع فائض كافٍ"
+
+[footer]
+
+[footer.community]
+greenEnergy = "شمسي"
+greenEnergySub1 = "مشحونة بـ evcc"
+greenEnergySub2 = "منذ أكتوبر ٢٠٢٢"
+greenShare = "حصة الطاقة الشمسية"
+greenShareSub1 = "القوة المقدمة من"
+greenShareSub2 = "تخزين الطاقة الشمسية وتخزين البطارية"
+power = "قوة الشحن"
+powerSub1 = "{activeClients} من إجمالي {totalClients} مشاركين"
+powerSub2 = "الشحن ..."
+tabTitle = "مجتمع مباشر"
+
+[footer.savings]
+footerLong = "{percent}٪ من الطاقة الشمسية"
+footerShort = "{percent}٪ شمسي\""
diff --git a/i18n/ca.toml b/i18n/ca.toml
index 29113f01..ee730a6e 100644
--- a/i18n/ca.toml
+++ b/i18n/ca.toml
@@ -142,9 +142,9 @@ pvProduction = "Producció"
 selfConsumption = "Autoconsum"
 
 [main.loadpoint]
-avgPrice = "Preu Ø"
+avgPrice = "Preu ⌀"
 charged = "Carregat"
-co2 = "CO₂ Ø "
+co2 = "CO₂ ⌀ "
 duration = "Duració"
 fallbackName = "Punt de càrrega"
 power = "Potència"
@@ -209,7 +209,7 @@ update = "Actualitzar"
 
 [main.targetChargePlan]
 chargeDuration = "Temps de càrrega"
-co2Label = "Emissió de CO₂ Ø"
+co2Label = "Emissió de CO₂ ⌀"
 priceLabel = "Preu de l'energia"
 timeRange = "{day} {range} hora"
 unknownPrice = "encara desconegut"
@@ -279,7 +279,7 @@ started = "Comenzó"
 title = "Cargando la sesión"
 
 [sessions]
-avgPrice = "Preu Ø"
+avgPrice = "Preu ⌀"
 co2 = "CO₂"
 date = "Període"
 downloadCsv = "Descarregar com a CSV"
diff --git a/i18n/da.toml b/i18n/da.toml
index 0dbdb45b..233fbaf3 100644
--- a/i18n/da.toml
+++ b/i18n/da.toml
@@ -178,7 +178,7 @@ update = "Opdatering"
 
 [main.targetChargePlan]
 chargeDuration = "Opladningstid"
-co2Label = "CO₂ udledning Ø"
+co2Label = "CO₂ udledning ⌀"
 priceLabel = "Energi pris"
 timeRange = "{day} {range} t"
 unknownPrice = "stadig ukendt"
diff --git a/i18n/de.toml b/i18n/de.toml
index 6e02f717..dd946ec8 100644
--- a/i18n/de.toml
+++ b/i18n/de.toml
@@ -10,7 +10,7 @@ legendTopAutostart = "startet automatisch"
 legendTopName = "batterieunterstütztes Laden"
 legendTopSubline = "ohne Unterbrechungen"
 modalTitle = "Batterieeinstellungen"
-note = "Hinweis: Nur relevant im PV Modus. Das Ladeverhalten wird entsprechend angepasst."
+note = "Hinweis: Nur relevant im PV-Modus. Das Ladeverhalten wird entsprechend angepasst."
 
 [batterySettings.bufferStart]
 full = "wenn fast voll"
@@ -29,7 +29,7 @@ greenShareSub1 = "der Leistung kommt"
 greenShareSub2 = "von PV und Speicher"
 power = "Ladeleistung"
 powerSub1 = "{activeClients} von {totalClients} Nutzern"
-powerSub2 = "laden..."
+powerSub2 = "laden ..."
 tabTitle = "Live-Community"
 
 [footer.savings]
@@ -52,9 +52,9 @@ tabTitle = "Meine Daten"
 becomeSponsor = "Sponsor werden"
 confetti = "Lust auf Konfetti?"
 confettiPromise = "Es gibt auch Sticker und digitales Konfetti"
-sticker = "…oder evcc Aufkleber?"
+sticker = "… oder evcc Sticker?"
 supportUs = "Unsere Mission: Sonne tanken zum Standard machen. Hilf uns und unterstütze evcc finanziell."
-thanks = "Vielen Dank, {sponsor}! Dein Beitrag hilft, evcc weiter zu entwickeln."
+thanks = "Vielen Dank, {sponsor}! Dein Beitrag hilft, evcc weiterzuentwickeln."
 titleNoSponsor = "Unterstütze uns"
 titleSponsor = "Du bist Unterstützer"
 
@@ -73,7 +73,7 @@ modalNoReleaseNotes = "Keine Versionshinweise verfügbar. Mehr Informationen zur
 modalTitle = "Neue Version verfügbar"
 modalUpdate = "Installieren"
 modalUpdateNow = "Jetzt installieren"
-modalUpdateStarted = "Starte die neue Version von evcc..."
+modalUpdateStarted = "Starte die neue Version von evcc ..."
 modalUpdateStatusStart = "Installation gestartet:"
 
 [gridSettings]
@@ -92,7 +92,7 @@ about = "Über"
 blog = "Blog"
 docs = "Dokumentation"
 github = "GitHub"
-login = "Fahrzeug Logins"
+login = "Fahrzeug-Logins"
 needHelp = "Hilfe benötigt?"
 sessions = "Ladevorgänge"
 settings = "Einstellungen"
@@ -105,7 +105,7 @@ issueDescription = "Komisches Verhalten gefunden?"
 modalTitle = "Hilfe benötigt?"
 primaryActions = "Funktioniert etwas nicht so, wie es soll? Dies sind gute Anlaufstellen, um Hilfe zu erhalten."
 restartButton = "Neu starten"
-restartDescription = "Schon einen Neustart versucht?"
+restartDescription = "Schon versucht, das Gerät aus- und wieder einzuschalten?"
 secondaryActions = "Noch keine Lösung gefunden? Hier noch ein paar weitere Möglichkeiten."
 
 [help.restart]
@@ -142,9 +142,9 @@ pvProduction = "Erzeugung"
 selfConsumption = "Eigenverbrauch"
 
 [main.loadpoint]
-avgPrice = "Ø Preis"
+avgPrice = "⌀ Preis"
 charged = "Geladen"
-co2 = "Ø CO₂"
+co2 = "⌀ CO₂"
 duration = "Dauer"
 fallbackName = "Ladepunkt"
 power = "Leistung"
@@ -169,7 +169,7 @@ label = "Max. Ladestrom"
 label = "Min. Ladestrom"
 
 [main.loadpointSettings.minSoc]
-description = "Reichweite für Notfälle. Fahrzeug wird im PV-Modus „schnell” auf {0}% geladen. Danach weiter mit PV-Überschuss."
+description = "Reichweite für Notfälle. Fahrzeug wird im PV-Modus „schnell“ auf {0}% geladen. Danach weiter mit PV-Überschuss."
 label = "Min. Ladung %"
 
 [main.loadpointSettings.phasesConfigured]
@@ -205,7 +205,7 @@ priceLimit = "Preisgrenze von {price}"
 remove = "Entfernen"
 setTargetTime = "keine"
 targetIsInThePast = "Wähle einen Zeitpunkt in der Zukunft, Marty."
-targetIsTooFarInTheFuture = "Wir passen den Plan an sobald wir mehr über die Zukunft wissen."
+targetIsTooFarInTheFuture = "Wir passen den Plan an, sobald wir mehr über die Zukunft wissen."
 title = "Zielzeit"
 today = "heute"
 tomorrow = "morgen"
@@ -213,7 +213,7 @@ update = "Aktualisieren"
 
 [main.targetChargePlan]
 chargeDuration = "Ladezeit"
-co2Label = "CO₂-Emission Ø"
+co2Label = "CO₂-Emission ⌀"
 priceLabel = "Energiepreis"
 timeRange = "{day} {range} Uhr"
 unknownPrice = "noch unbekannt"
@@ -225,7 +225,7 @@ noLimit = "keins"
 [main.vehicle]
 addVehicle = "Fahrzeug hinzufügen"
 changeVehicle = "Fahrzeug ändern"
-detectionActive = "Fahrzeugerkennung läuft..."
+detectionActive = "Fahrzeugerkennung läuft …"
 fallbackName = "Fahrzeug"
 moreActions = "Weitere Aktionen"
 none = "Kein Fahrzeug"
@@ -241,24 +241,24 @@ ready = "bereit"
 vehicleTarget = "Fahrzeuglimit: {soc}%"
 
 [main.vehicleStatus]
-charging = "Ladevorgang aktiv…"
-cheapEnergyCharging = "Günstige Energie verfügbar. Ladevorgang aktiv…"
-cleanEnergyCharging = "Saubere Energie verfügbar. Ladevorgang aktiv…"
+charging = "Ladevorgang aktiv …"
+cheapEnergyCharging = "Günstige Energie verfügbar. Ladevorgang aktiv …"
+cleanEnergyCharging = "Saubere Energie verfügbar. Ladevorgang aktiv …"
 climating = "Vorklimatisierung erkannt."
 connected = "Verbunden."
 disconnected = "Nicht verbunden."
 guard = "Schaltschutz aktiv für {remaining}."
 minCharge = "Mindestladung bis {soc}%."
-pvDisable = "Zu wenig Überschuss. Pausiere in {remaining}…"
-pvEnable = "Überschuss verfügbar. Starte in {remaining}…"
-scale1p = "Reduziere auf einphasig in {remaining}…"
-scale3p = "Erhöhe auf dreiphasig in {remaining}…"
-targetChargeActive = "Zielladen aktiv…"
+pvDisable = "Zu wenig Überschuss. Pausiere in {remaining} …"
+pvEnable = "Überschuss verfügbar. Starte in {remaining} …"
+scale1p = "Reduziere auf einphasig in {remaining} …"
+scale3p = "Erhöhe auf dreiphasig in {remaining} …"
+targetChargeActive = "Zielladen aktiv …"
 targetChargePlanned = "Zielladen geplant. Ladung startet {time} Uhr."
-targetChargeWaitForVehicle = "Zielladen bereit. Warte auf Fahrzeug…"
+targetChargeWaitForVehicle = "Zielladen bereit. Warte auf Fahrzeug …"
 unknown = ""
 vehicleTargetReached = "Fahrzeuglimit {soc}% erreicht."
-waitForVehicle = "Ladebereit. Warte auf Fahrzeug…"
+waitForVehicle = "Ladebereit. Warte auf Fahrzeug …"
 
 [notifications]
 dismissAll = "Meldungen entfernen"
@@ -283,8 +283,8 @@ started = "Startzeit"
 title = "Ladevorgang"
 
 [sessions]
-avgPrice = "Ø Preis"
-co2 = "Ø CO₂"
+avgPrice = "⌀ Preis"
+co2 = "⌀ CO₂"
 csvMonth = "Download {month} CSV"
 csvTotal = "Gesamte CSV herunterladen"
 date = "Anfang"
@@ -293,7 +293,7 @@ energy = "Geladen"
 loadpoint = "Ladepunkt"
 noData = "Noch keine Ladevorgänge in diesem Monat."
 price = "Σ Preis"
-reallyDelete = "Wollen Sie diesen Ladevorgang wirklich löschen?"
+reallyDelete = "Möchtest du diesen Ladevorgang wirklich löschen?"
 solar = "Sonne"
 title = "Ladevorgänge"
 vehicle = "Fahrzeug"
@@ -312,7 +312,7 @@ vehicle = "Fahrzeug"
 [sessions.filter]
 allLoadpoints = "Alle Ladepunkte"
 allVehicles = "Alle Fahrzeuge"
-filter = "filtern"
+filter = "Filtern"
 
 [settings]
 title = "Einstellungen"
@@ -328,7 +328,7 @@ label = "Sprache"
 [settings.sponsorToken]
 expires = "Dein Sponsortoken läuft {inXDays} ab. {getNewToken} und aktualisiere deine Konfigurationsdatei."
 getNew = "Hol dir ein Neues"
-hint = "Hinweis: Wir werden das zukünfig automatisieren."
+hint = "Hinweis: Wir werden das zukünftig automatisieren."
 
 [settings.telemetry]
 label = "Telemetrie"
@@ -340,7 +340,7 @@ label = "Design"
 light = "Hell"
 
 [settings.unit]
-km = "Km"
+km = "Kilometer"
 label = "Einheit"
 mi = "Meilen"
 
@@ -350,7 +350,7 @@ configuration = "Konfiguration"
 description = "Bitte überprüfe deine Konfigurationsdatei. Sollte dir die Fehlermeldung nicht weiterhelfen, suche in unseren {0} nach einer Lösung."
 discussions = "GitHub Diskussionen"
 fixAndRestart = "Behebe das Problem und starte den Server neu."
-hint = "Hinweis: Ein weiterer Grund könnte ein fehlerhaftes Gerät (Wechselrichter, Zähler, ...) sein. Überprüfe deine Netzwerkverbindungen."
+hint = "Hinweis: Ein weiterer Grund könnte ein fehlerhaftes Gerät (Wechselrichter, Zähler, …) sein. Überprüfe deine Netzwerkverbindungen."
 lineError = "In {0} wurde ein Fehler gefunden."
 lineErrorLink = "Zeile {0}"
 restartButton = "Neu starten"
diff --git a/i18n/en.toml b/i18n/en.toml
index 2096899d..0135834c 100644
--- a/i18n/en.toml
+++ b/i18n/en.toml
@@ -106,12 +106,12 @@ modalTitle = "Need help?"
 primaryActions = "Something does not work the way it supposed to do? These are good places to get help."
 restartButton = "Restart"
 restartDescription = "Tried turning it off and on again?"
-secondaryActions = "Still not able to solve your problem? Here are some more heavy handed options."
+secondaryActions = "Still not able to solve your problem? Here are some more heavy-handed options."
 
 [help.restart]
 cancel = "Cancel"
 confirm = "Yes, restart!"
-description = "Under normal circumsances restarting should not be necessary. Please consider filing a bug if you need to restart evcc on a regular basis."
+description = "Under normal circumstances restarting should not be necessary. Please consider filing a bug if you need to restart evcc on a regular basis."
 disclaimer = "Note: evcc will terminate and rely on the operating system to restart the service."
 modalTitle = "Are you sure you want to restart?"
 
@@ -142,9 +142,9 @@ pvProduction = "Production"
 selfConsumption = "Self-consumption"
 
 [main.loadpoint]
-avgPrice = "Ø Price"
+avgPrice = "⌀ Price"
 charged = "Charged"
-co2 = "Ø CO₂"
+co2 = "⌀ CO₂"
 duration = "Duration"
 fallbackName = "Charging point"
 power = "Power"
@@ -213,7 +213,7 @@ update = "Update"
 
 [main.targetChargePlan]
 chargeDuration = "Charging time"
-co2Label = "CO₂ emission Ø"
+co2Label = "CO₂ emission ⌀"
 priceLabel = "Energy price"
 timeRange = "{day} {range} h"
 unknownPrice = "still unknown"
@@ -283,11 +283,11 @@ started = "Started"
 title = "Charging Session"
 
 [sessions]
-avgPrice = "Ø Price"
-co2 = "Ø CO₂"
+avgPrice = "⌀ Price"
+co2 = "⌀ CO₂"
 csvMonth = "Download {month} CSV"
 csvTotal = "Download total CSV"
-date = "Finished"
+date = "Start"
 downloadCsv = "Download as CSV"
 energy = "Charged"
 loadpoint = "Charging point"
@@ -296,6 +296,7 @@ price = "Σ Price"
 reallyDelete = "Do you really want to delete this session?"
 solar = "Solar"
 title = "Charging Sessions"
+total = "Total"
 vehicle = "Vehicle"
 
 [sessions.csv]
@@ -312,7 +313,7 @@ vehicle = "Vehicle"
 [sessions.filter]
 allLoadpoints = "all charging points"
 allVehicles = "all vehicles"
-filter = "filter"
+filter = "Filter"
 
 [settings]
 title = "Settings"
diff --git a/i18n/es.toml b/i18n/es.toml
index da1264ed..585940aa 100644
--- a/i18n/es.toml
+++ b/i18n/es.toml
@@ -147,9 +147,9 @@ pvProduction = "Producción"
 selfConsumption = "Autoconsumo"
 
 [main.loadpoint]
-avgPrice = "Precio Ø"
+avgPrice = "Precio ⌀"
 charged = "Cargado"
-co2 = "CO₂ Ø "
+co2 = "CO₂ ⌀ "
 duration = "Duración"
 fallbackName = "Punto de carga"
 power = "Potencia"
@@ -220,7 +220,7 @@ update = "Actualizar"
 
 [main.targetChargePlan]
 chargeDuration = "Tiempo de carga"
-co2Label = "Emisión de CO₂ Ø"
+co2Label = "Emisión de CO₂ ⌀"
 priceLabel = "Precio de la energía"
 timeRange = "{day} {range} hora"
 unknownPrice = "aún desconocido"
@@ -292,11 +292,11 @@ started = "Comenzó"
 title = "Cargando la sesión"
 
 [sessions]
-avgPrice = "Precio Ø"
-co2 = "CO₂ Ø"
+avgPrice = "Precio ⌀"
+co2 = "CO₂ ⌀"
 csvMonth = "Descargar el {month} CSV"
 csvTotal = "Descargar todos los CSV"
-date = "Acabado"
+date = "Comenzar"
 downloadCsv = "Descargar como CSV"
 energy = "Cargado"
 loadpoint = "Punto de carga"
@@ -305,6 +305,7 @@ price = "Σ Precio"
 reallyDelete = "¿De verdad quieres eliminar esta sesión?"
 solar = "Solar"
 title = "Sesiones de carga"
+total = "Total"
 vehicle = "Vehículo"
 
 [sessions.csv]
@@ -321,7 +322,7 @@ vehicle = "Vehículo"
 [sessions.filter]
 allLoadpoints = "todos los puntos de recarga"
 allVehicles = "todos los vehiculos"
-filter = "filtrar"
+filter = "Filtrar"
 
 [settings]
 title = "Ajustes"
diff --git a/i18n/fi.toml b/i18n/fi.toml
index 0f556068..ce9cc8ff 100644
--- a/i18n/fi.toml
+++ b/i18n/fi.toml
@@ -142,9 +142,9 @@ pvProduction = "Tuotanto"
 selfConsumption = "Tuotannon kulutus"
 
 [main.loadpoint]
-avgPrice = "Ø Hinta"
+avgPrice = "⌀ Hinta"
 charged = "Ladattu"
-co2 = "Ø CO₂"
+co2 = "⌀ CO₂"
 duration = "Kesto"
 fallbackName = "Latauspiste"
 power = "Teho"
@@ -211,7 +211,7 @@ update = "Päivittää"
 
 [main.targetChargePlan]
 chargeDuration = "Latausaika"
-co2Label = "CO₂ päästö Ø"
+co2Label = "CO₂ päästö ⌀"
 priceLabel = "Energian hinta"
 timeRange = "{day} {range} t"
 unknownPrice = "ei vielä tiedossa"
@@ -281,8 +281,8 @@ started = "Alkoi"
 title = "Latausistunto"
 
 [sessions]
-avgPrice = "Ø Hinta"
-co2 = "Ø CO₂"
+avgPrice = "⌀ Hinta"
+co2 = "⌀ CO₂"
 date = "Valmis"
 downloadCsv = "Lataa CSV muodossa"
 energy = "Ladattu"
diff --git a/i18n/fr.toml b/i18n/fr.toml
index 1516a91f..d8ea4094 100644
--- a/i18n/fr.toml
+++ b/i18n/fr.toml
@@ -147,9 +147,9 @@ pvProduction = "Production"
 selfConsumption = "Consommation propre"
 
 [main.loadpoint]
-avgPrice = "Prix Ø"
+avgPrice = "Prix ⌀"
 charged = "Chargé"
-co2 = "CO₂ Ø"
+co2 = "CO₂ ⌀"
 duration = "Durée"
 fallbackName = "Point de chargement"
 power = "Puissance"
@@ -220,7 +220,7 @@ update = "Mettre à jour"
 
 [main.targetChargePlan]
 chargeDuration = "Durée de charge"
-co2Label = "Émissions de CO₂ Ø"
+co2Label = "Émissions de CO₂ ⌀"
 priceLabel = "Prix de l'énergie"
 timeRange = "{day} {range} h"
 unknownPrice = "encore inconnu"
@@ -290,8 +290,8 @@ started = "Début"
 title = "Session de charge"
 
 [sessions]
-avgPrice = "Prix Ø"
-co2 = "CO₂ Ø"
+avgPrice = "Prix ⌀"
+co2 = "CO₂ ⌀"
 csvMonth = "Télécharger CSV {month}"
 csvTotal = "Télécharger CSV total"
 date = "Début session"
diff --git a/i18n/hr.toml b/i18n/hr.toml
index 738c9d1f..8e9bc6ea 100644
--- a/i18n/hr.toml
+++ b/i18n/hr.toml
@@ -143,9 +143,9 @@ pvProduction = "Produkcija"
 selfConsumption = "Samopotrošnja"
 
 [main.loadpoint]
-avgPrice = "Ø cijena"
+avgPrice = "⌀ cijena"
 charged = "Napojeno"
-co2 = "Ø CO₂"
+co2 = "⌀ CO₂"
 duration = "Trajanje"
 fallbackName = "Mjesto napajanja"
 power = "Energija"
@@ -212,7 +212,7 @@ update = "Ažuriraj"
 
 [main.targetChargePlan]
 chargeDuration = "Vrijeme napajanja"
-co2Label = "Ø CO₂ emisije"
+co2Label = "⌀ CO₂ emisije"
 priceLabel = "Cijena energije"
 timeRange = "{day} {range} h"
 unknownPrice = "jos nepoznato"
@@ -282,8 +282,8 @@ started = "Počelo"
 title = "Sesije napajanja"
 
 [sessions]
-avgPrice = "Ø cijena"
-co2 = "Ø CO₂"
+avgPrice = "⌀ cijena"
+co2 = "⌀ CO₂"
 csvMonth = "Preuzmi CSV za {month}"
 csvTotal = "Preuzmi CSV za ukupno"
 date = "Gotovo"
diff --git a/i18n/lb.toml b/i18n/lb.toml
index 2e3b2394..1930c00f 100644
--- a/i18n/lb.toml
+++ b/i18n/lb.toml
@@ -148,7 +148,7 @@ update = "Aktualiséieren"
 
 [main.targetChargePlan]
 chargeDuration = "Opluedzäit"
-co2Label = "CO₂ Emissioun Ø"
+co2Label = "CO₂ Emissioun ⌀"
 priceLabel = "Energiepräis"
 timeRange = "{day} {range} Auer"
 unknownPrice = "nach onbekannt"
diff --git a/i18n/lt.toml b/i18n/lt.toml
index a2495a9a..edd8a99a 100644
--- a/i18n/lt.toml
+++ b/i18n/lt.toml
@@ -116,7 +116,7 @@ secondaryActions = "Vis dar nepavyksta išspręsti problemos? Čia rasite drasti
 [help.restart]
 cancel = "Atšaukti"
 confirm = "Taip, restartuoti!"
-description = "Įprastai restartavimas neturėtų būti reikalingas. Jei jums reikia vis restartuoti, pasvarstykite - gal derėtų pranešti apie problemą."
+description = "Įprastai restartavimas neturėtų būti reikalingas. Jei jums vis reikia restartuoti, pasvarstykite, gal derėtų pranešti apie problemą."
 disclaimer = "Pastaba: evcc sustos, ir pasitikės operacinės sistemos pagalba paleidžiant iš naujo."
 modalTitle = "Ar tikrai norite restartuoti?"
 
@@ -147,9 +147,9 @@ pvProduction = "Gamyba"
 selfConsumption = "Sunaudojama iškart"
 
 [main.loadpoint]
-avgPrice = "Ø Kaina"
+avgPrice = "⌀ Kaina"
 charged = "Įkrauta"
-co2 = "Ø CO₂"
+co2 = "⌀ CO₂"
 duration = "Trukmė"
 fallbackName = "Įkroviklis"
 power = "Galia"
@@ -220,7 +220,7 @@ update = "Atnaujinti"
 
 [main.targetChargePlan]
 chargeDuration = "Įkrovimo trukmė"
-co2Label = "CO₂ emisijos Ø"
+co2Label = "CO₂ emisijos ⌀"
 priceLabel = "Energijos kaina"
 timeRange = "{day} {range} val"
 unknownPrice = "dar nežinoma"
@@ -292,11 +292,11 @@ started = "Pradėta"
 title = "Įkrovimo sesija"
 
 [sessions]
-avgPrice = "Ø Kaina"
-co2 = "Ø CO₂"
+avgPrice = "⌀ Kaina"
+co2 = "⌀ CO₂"
 csvMonth = "Atsisiųsti {month} CSV"
 csvTotal = "Atsisiųsti visą CSV"
-date = "Pabaigta"
+date = "Pradžia"
 downloadCsv = "Atsisiųsti CSV failą"
 energy = "Įkrauta"
 loadpoint = "Įkroviklis"
@@ -305,6 +305,7 @@ price = "Σ Kaina"
 reallyDelete = "Ar tikrai norite ištrinti šią įkrovimo sesiją?"
 solar = "Saulės"
 title = "Įkrovimo Sesijos"
+total = "Viso"
 vehicle = "Automobilis"
 
 [sessions.csv]
@@ -321,7 +322,7 @@ vehicle = "Automobilis"
 [sessions.filter]
 allLoadpoints = "visi įkrovikliai"
 allVehicles = "visi automobiliai"
-filter = "filtruoti"
+filter = "Filtruoti"
 
 [settings]
 title = "Nustatymai"
diff --git a/i18n/nl.toml b/i18n/nl.toml
index d37f093b..db42e397 100644
--- a/i18n/nl.toml
+++ b/i18n/nl.toml
@@ -147,9 +147,9 @@ pvProduction = "Productie"
 selfConsumption = "Zelfverbruik"
 
 [main.loadpoint]
-avgPrice = "Ø Prijs"
+avgPrice = "⌀ Prijs"
 charged = "Geladen"
-co2 = "Ø CO₂"
+co2 = "⌀ CO₂"
 duration = "Duur"
 fallbackName = "Laadpunt"
 power = "Vermogen"
@@ -220,7 +220,7 @@ update = "Updaten"
 
 [main.targetChargePlan]
 chargeDuration = "Oplaadtijd"
-co2Label = "CO₂ emissie Ø"
+co2Label = "CO₂ emissie ⌀"
 priceLabel = "Energieprijs"
 timeRange = "{day} {range} h"
 unknownPrice = "nog onbekend"
@@ -290,8 +290,8 @@ started = "Gestart"
 title = "Oplaadsessie"
 
 [sessions]
-avgPrice = "Ø Prijs"
-co2 = "Ø CO₂"
+avgPrice = "⌀ Prijs"
+co2 = "⌀ CO₂"
 csvMonth = "Download CSV van {month}"
 csvTotal = "Download totale CSV"
 date = "Start"
diff --git a/i18n/no.toml b/i18n/no.toml
index d5974ede..40d89166 100644
--- a/i18n/no.toml
+++ b/i18n/no.toml
@@ -147,9 +147,9 @@ pvProduction = "Produksjon"
 selfConsumption = "Eget forbruk"
 
 [main.loadpoint]
-avgPrice = "Ø-pris"
+avgPrice = "⌀-pris"
 charged = "Oppladet"
-co2 = "Ø-CO₂"
+co2 = "⌀-CO₂"
 duration = "Varighet"
 fallbackName = "Ladepunkt"
 power = "Effekt"
@@ -216,7 +216,7 @@ update = "Oppdater"
 
 [main.targetChargePlan]
 chargeDuration = "Ladetid"
-co2Label = "CO₂-utslipp Ø"
+co2Label = "CO₂-utslipp ⌀"
 priceLabel = "Energipris"
 timeRange = "{day} {range} t"
 unknownPrice = "fremdeles ukjent"
@@ -286,8 +286,8 @@ started = "Startet"
 title = "Ladeøkt"
 
 [sessions]
-avgPrice = "Ø-pris"
-co2 = "Ø-CO₂"
+avgPrice = "⌀-pris"
+co2 = "⌀-CO₂"
 csvMonth = "Last ned (måned) CSV"
 csvTotal = "Last ned total CSV"
 date = "Start"
@@ -299,6 +299,7 @@ price = "Σ-pris"
 reallyDelete = "Slett denne økten?"
 solar = "Solenergi"
 title = "Ladeøkter"
+total = "Totalt"
 vehicle = "Kjøretøy"
 
 [sessions.csv]
diff --git a/i18n/pl.toml b/i18n/pl.toml
index b433d5a0..a19112c8 100644
--- a/i18n/pl.toml
+++ b/i18n/pl.toml
@@ -147,9 +147,9 @@ pvProduction = "Produkcja"
 selfConsumption = "Konsumpcja własna"
 
 [main.loadpoint]
-avgPrice = "Ø Cena"
+avgPrice = "⌀ Cena"
 charged = "Naładowano"
-co2 = "Ø CO₂"
+co2 = "⌀ CO₂"
 duration = "Czas trwania"
 fallbackName = "Ładowarka"
 power = "Moc"
@@ -220,7 +220,7 @@ update = "Aktualizować"
 
 [main.targetChargePlan]
 chargeDuration = "Czas ładowania"
-co2Label = "CO₂ emisja Ø"
+co2Label = "CO₂ emisja ⌀"
 priceLabel = "Cena energii"
 timeRange = "{day} {range} h"
 unknownPrice = "nadal nieznany"
@@ -290,11 +290,11 @@ started = "Zaczęło się"
 title = "Sesja ładowania"
 
 [sessions]
-avgPrice = "Ø Cena"
-co2 = "Ø CO₂"
+avgPrice = "⌀ Cena"
+co2 = "⌀ CO₂"
 csvMonth = "Pobierz plik {month} CSV"
 csvTotal = "Pobierz plik CSV"
-date = "Skończone"
+date = "Rozpoczęty"
 downloadCsv = "Pobierz jako CSV"
 energy = "Naładowano"
 loadpoint = "Ładowarka"
@@ -303,6 +303,7 @@ price = "Σ Cena"
 reallyDelete = "Czy na pewno chcesz usunąć tę sesję?"
 solar = "Słoneczna"
 title = "Sesje ładowania"
+total = "Totalny"
 vehicle = "Pojazd"
 
 [sessions.csv]
@@ -319,7 +320,7 @@ vehicle = "Pojazd"
 [sessions.filter]
 allLoadpoints = "wszystkie punkty ładowania"
 allVehicles = "wszystkie pojazdy"
-filter = "filtrować"
+filter = "Filtrować"
 
 [settings]
 title = "Ustawienia"
diff --git a/i18n/pt.toml b/i18n/pt.toml
index 82dd5185..2d8d23c9 100644
--- a/i18n/pt.toml
+++ b/i18n/pt.toml
@@ -147,9 +147,9 @@ pvProduction = "Produção"
 selfConsumption = "Consumo próprio"
 
 [main.loadpoint]
-avgPrice = "Preço Ø"
+avgPrice = "Preço ⌀"
 charged = "Carregado"
-co2 = "CO₂ Ø"
+co2 = "CO₂ ⌀"
 duration = "Duração"
 fallbackName = "Ponto de carga"
 power = "Desempenho"
@@ -220,7 +220,7 @@ update = "Atualizar"
 
 [main.targetChargePlan]
 chargeDuration = "Tempo de carga"
-co2Label = "Emissão de CO₂ Ø"
+co2Label = "Emissão de CO₂ ⌀"
 priceLabel = "Preço da energia"
 timeRange = "{day} {range} h"
 unknownPrice = "ainda desconhecido"
@@ -292,11 +292,11 @@ started = "Começado"
 title = "Sessão de carga"
 
 [sessions]
-avgPrice = "Preço Ø"
-co2 = "CO₂ Ø"
+avgPrice = "Preço ⌀"
+co2 = "CO₂ ⌀"
 csvMonth = "Download {month} CSV"
 csvTotal = "Download tudo CSV"
-date = "Terminado"
+date = "Início"
 downloadCsv = "Download como CSV"
 energy = "Carregado"
 loadpoint = "Ponto de carga"
@@ -306,6 +306,7 @@ price = "Preço Σ"
 reallyDelete = "Você realmente quer excluir esta sessão?"
 solar = "Sol"
 title = "Sessões de carga"
+total = "Total"
 vehicle = "Veículo"
 
 [sessions.csv]
@@ -322,7 +323,7 @@ vehicle = "Veículo"
 [sessions.filter]
 allLoadpoints = "todos pontos de carregamento"
 allVehicles = "todos veículos"
-filter = "filtro"
+filter = "Filtro"
 
 [settings]
 title = "Configuração"
diff --git a/i18n/ro.toml b/i18n/ro.toml
index 6a83bf9b..d70b81b3 100644
--- a/i18n/ro.toml
+++ b/i18n/ro.toml
@@ -199,7 +199,7 @@ update = "Actualizare"
 
 [main.targetChargePlan]
 chargeDuration = "Timp de încărcare"
-co2Label = "Emisie CO₂ Ø"
+co2Label = "Emisie CO₂ ⌀"
 priceLabel = "Prețul energiei"
 timeRange = "{day} {range} h"
 unknownPrice = "încă necunoscut"
diff --git a/i18n/sl.toml b/i18n/sl.toml
index 1dde908c..4952f826 100644
--- a/i18n/sl.toml
+++ b/i18n/sl.toml
@@ -142,9 +142,9 @@ pvProduction = "Proizvodnja"
 selfConsumption = "Samoporaba"
 
 [main.loadpoint]
-avgPrice = "Ø Cena"
+avgPrice = "⌀ Cena"
 charged = "Napolnjeno"
-co2 = "Ø CO₂"
+co2 = "⌀ CO₂"
 duration = "Trajanje"
 fallbackName = "Polnilno mesto"
 power = "Moč"
@@ -211,7 +211,7 @@ update = "Posodobitev"
 
 [main.targetChargePlan]
 chargeDuration = "Čas polnjenja"
-co2Label = "Emisije CO₂ Ø"
+co2Label = "Emisije CO₂ ⌀"
 priceLabel = "Cena energije"
 timeRange = "{day} {range} h"
 unknownPrice = "še vedno neznano"
@@ -281,8 +281,8 @@ started = "Začetek"
 title = "Seja polnjenja"
 
 [sessions]
-avgPrice = "Ø Cena"
-co2 = "Ø CO₂"
+avgPrice = "⌀ Cena"
+co2 = "⌀ CO₂"
 date = "Končano"
 downloadCsv = "Prenesi kot CSV"
 energy = "Napolnjeno"
diff --git a/i18n/sv.toml b/i18n/sv.toml
index 6dadbb66..0066e72d 100644
--- a/i18n/sv.toml
+++ b/i18n/sv.toml
@@ -187,7 +187,7 @@ update = "Uppdatering"
 
 [main.targetChargePlan]
 chargeDuration = "Laddningstid"
-co2Label = "CO₂-utsläpp Ø"
+co2Label = "CO₂-utsläpp ⌀"
 priceLabel = "Energipris"
 timeRange = "{day} {range} h"
 unknownPrice = "fortfarande okänd"
diff --git a/i18n/uk.toml b/i18n/uk.toml
index c0ba81f0..fbc61baf 100644
--- a/i18n/uk.toml
+++ b/i18n/uk.toml
@@ -107,7 +107,7 @@ modalTitle = "Потрібна допомога?"
 primaryActions = "Щось не працює так, як повинно? Це хороші місця, де можна отримати допомогу."
 restartButton = "Перезапустити"
 restartDescription = "Спробували вимкнути і знову увімкнути?"
-secondaryActions = "Все ще не можете вирішити свою проблему? Ось кілька більш жорстких варіантів."
+secondaryActions = "Все ще не можете вирішити свою проблему? Ось кілька жорсткіших варіантів."
 
 [help.restart]
 cancel = "Скасувати"
@@ -143,9 +143,9 @@ pvProduction = "Виробництво"
 selfConsumption = "Власне споживання"
 
 [main.loadpoint]
-avgPrice = "Ø Ціна"
+avgPrice = "⌀ Ціна"
 charged = "Заряджено"
-co2 = "Ø CO₂"
+co2 = "⌀ CO₂"
 duration = "Тривалість"
 fallbackName = "Точка зарядки"
 power = "Потужність"
@@ -213,7 +213,7 @@ update = "Оновлення"
 
 [main.targetChargePlan]
 chargeDuration = "Час зарядки"
-co2Label = "Викиди CO₂ Ø"
+co2Label = "Викиди CO₂ ⌀"
 priceLabel = "Ціна на енергію"
 timeRange = "{day} {range} год"
 unknownPrice = "ще невідомо"
@@ -285,11 +285,11 @@ started = "Розпочато"
 title = "Сеанс зарядки"
 
 [sessions]
-avgPrice = "Ø Ціна"
-co2 = "Ø CO₂"
+avgPrice = "⌀ Ціна"
+co2 = "⌀ CO₂"
 csvMonth = "Завантажити {month} CSV"
 csvTotal = "Завантажити повний CSV"
-date = "Завершено"
+date = "Почати"
 downloadCsv = "Завантажити як CSV"
 energy = "Заряджено"
 loadpoint = "Точка зарядки"
@@ -298,6 +298,7 @@ price = "Σ Ціна"
 reallyDelete = "Ви дійсно хочете видалити цей сеанс?"
 solar = "Сонячна"
 title = "Сеанси зарядки"
+total = "Всього"
 vehicle = "Транспортний засіб"
 
 [sessions.csv]
@@ -314,7 +315,7 @@ vehicle = "Транспортний засіб"
 [sessions.filter]
 allLoadpoints = "всі точки зарядки"
 allVehicles = "всі транспортні засоби"
-filter = "фільтр"
+filter = "Фільтр"
 
 [settings]
 title = "Налаштування"
diff --git a/meter/tapo/connection.go b/meter/tapo/connection.go
index 49e24f49..ef81c4cc 100644
--- a/meter/tapo/connection.go
+++ b/meter/tapo/connection.go
@@ -292,6 +292,17 @@ func (d *Connection) DoSecureRequest(uri string, taporequest map[string]interfac
 		return nil, err
 	}
 
+	// Login atempt in case of tapo switch connection hicups
+	if res.ErrorCode == 9999 {
+		if err := d.Login(); err != nil {
+			return nil, err
+		}
+
+		if err := d.DoJSON(req, &res); err != nil {
+			return nil, err
+		}
+	}
+
 	if err := d.CheckErrorCode(res.ErrorCode); err != nil {
 		return nil, err
 	}
diff --git a/package-lock.json b/package-lock.json
index 50d9626b..2bd8dcf4 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -24,20 +24,20 @@
         "eslint-config-prettier": "^8.5.0",
         "eslint-plugin-import": "^2.25.3",
         "eslint-plugin-node": "^11.1.0",
-        "eslint-plugin-prettier": "^4.0.0",
+        "eslint-plugin-prettier": "^5.0.0-alpha.1",
         "eslint-plugin-promise": "^6.0.0",
         "eslint-plugin-vue": "^9.3.0",
-        "happy-dom": "^9.5.0",
+        "happy-dom": "^10.0.3",
         "histoire": "^0.16.1",
         "json-to-pretty-yaml": "^1.2.2",
         "markty-toml": "^0.1.1",
-        "prettier": "^2.5.1",
+        "prettier": "^3.0.0",
         "prettier-plugin-toml": "^0.3.1",
         "rollup-plugin-visualizer": "^5.8.3",
         "smoothscroll-polyfill": "^0.4.4",
         "vite": "^4.2.1",
         "vite-plugin-toml": "^0.5.0",
-        "vitest": "^0.32.2",
+        "vitest": "^0.33.0",
         "vue": "^3.2.29",
         "vue-eslint-parser": "^9.1.0",
         "vue-hot-reload-api": "^2.3.4",
@@ -51,6 +51,14 @@
         "npm": ">=8.0.0"
       }
     },
+    "node_modules/@aashutoshrathi/word-wrap": {
+      "version": "1.2.6",
+      "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz",
+      "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
     "node_modules/@akryum/tinypool": {
       "version": "0.3.1",
       "resolved": "https://registry.npmjs.org/@akryum/tinypool/-/tinypool-0.3.1.tgz",
@@ -83,33 +91,33 @@
       }
     },
     "node_modules/@babel/compat-data": {
-      "version": "7.22.5",
-      "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.5.tgz",
-      "integrity": "sha512-4Jc/YuIaYqKnDDz892kPIledykKg12Aw1PYX5i/TY28anJtacvM1Rrr8wbieB9GfEJwlzqT0hUEao0CxEebiDA==",
+      "version": "7.22.6",
+      "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.6.tgz",
+      "integrity": "sha512-29tfsWTq2Ftu7MXmimyC0C5FDZv5DYxOZkh3XD3+QW4V/BYuv/LyEsjj3c0hqedEaDt6DBfDvexMKU8YevdqFg==",
       "engines": {
         "node": ">=6.9.0"
       }
     },
     "node_modules/@babel/core": {
-      "version": "7.22.5",
-      "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.5.tgz",
-      "integrity": "sha512-SBuTAjg91A3eKOvD+bPEz3LlhHZRNu1nFOVts9lzDJTXshHTjII0BAtDS3Y2DAkdZdDKWVZGVwkDfc4Clxn1dg==",
+      "version": "7.22.8",
+      "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.8.tgz",
+      "integrity": "sha512-75+KxFB4CZqYRXjx4NlR4J7yGvKumBuZTmV4NV6v09dVXXkuYVYLT68N6HCzLvfJ+fWCxQsntNzKwwIXL4bHnw==",
       "dependencies": {
         "@ampproject/remapping": "^2.2.0",
         "@babel/code-frame": "^7.22.5",
-        "@babel/generator": "^7.22.5",
-        "@babel/helper-compilation-targets": "^7.22.5",
+        "@babel/generator": "^7.22.7",
+        "@babel/helper-compilation-targets": "^7.22.6",
         "@babel/helper-module-transforms": "^7.22.5",
-        "@babel/helpers": "^7.22.5",
-        "@babel/parser": "^7.22.5",
+        "@babel/helpers": "^7.22.6",
+        "@babel/parser": "^7.22.7",
         "@babel/template": "^7.22.5",
-        "@babel/traverse": "^7.22.5",
+        "@babel/traverse": "^7.22.8",
         "@babel/types": "^7.22.5",
+        "@nicolo-ribaudo/semver-v6": "^6.3.3",
         "convert-source-map": "^1.7.0",
         "debug": "^4.1.0",
         "gensync": "^1.0.0-beta.2",
-        "json5": "^2.2.2",
-        "semver": "^6.3.0"
+        "json5": "^2.2.2"
       },
       "engines": {
         "node": ">=6.9.0"
@@ -120,9 +128,9 @@
       }
     },
     "node_modules/@babel/generator": {
-      "version": "7.22.5",
-      "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.5.tgz",
-      "integrity": "sha512-+lcUbnTRhd0jOewtFSedLyiPsD5tswKkbgcezOqqWFUVNEwoUTlpPOBmvhG7OXWLR4jMdv0czPGH5XbflnD1EA==",
+      "version": "7.22.7",
+      "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.7.tgz",
+      "integrity": "sha512-p+jPjMG+SI8yvIaxGgeW24u7q9+5+TGpZh8/CuB7RhBKd7RCy8FayNEFNNKrNK/eUcY/4ExQqLmyrvBXKsIcwQ==",
       "dependencies": {
         "@babel/types": "^7.22.5",
         "@jridgewell/gen-mapping": "^0.3.2",
@@ -156,15 +164,15 @@
       }
     },
     "node_modules/@babel/helper-compilation-targets": {
-      "version": "7.22.5",
-      "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.5.tgz",
-      "integrity": "sha512-Ji+ywpHeuqxB8WDxraCiqR0xfhYjiDE/e6k7FuIaANnoOFxAHskHChz4vA1mJC9Lbm01s1PVAGhQY4FUKSkGZw==",
+      "version": "7.22.6",
+      "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.6.tgz",
+      "integrity": "sha512-534sYEqWD9VfUm3IPn2SLcH4Q3P86XL+QvqdC7ZsFrzyyPF3T4XGiVghF6PTYNdWg6pXuoqXxNQAhbYeEInTzA==",
       "dependencies": {
-        "@babel/compat-data": "^7.22.5",
+        "@babel/compat-data": "^7.22.6",
         "@babel/helper-validator-option": "^7.22.5",
-        "browserslist": "^4.21.3",
-        "lru-cache": "^5.1.1",
-        "semver": "^6.3.0"
+        "@nicolo-ribaudo/semver-v6": "^6.3.3",
+        "browserslist": "^4.21.9",
+        "lru-cache": "^5.1.1"
       },
       "engines": {
         "node": ">=6.9.0"
@@ -174,9 +182,9 @@
       }
     },
     "node_modules/@babel/helper-create-class-features-plugin": {
-      "version": "7.22.5",
-      "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.5.tgz",
-      "integrity": "sha512-xkb58MyOYIslxu3gKmVXmjTtUPvBU4odYzbiIQbWwLKIHCsx6UGZGX6F1IznMFVnDdirseUZopzN+ZRt8Xb33Q==",
+      "version": "7.22.6",
+      "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.6.tgz",
+      "integrity": "sha512-iwdzgtSiBxF6ni6mzVnZCF3xt5qE6cEA0J7nFt8QOAWZ0zjCFceEgpn3vtb2V7WFR6QzP2jmIFOHMTRo7eNJjQ==",
       "dependencies": {
         "@babel/helper-annotate-as-pure": "^7.22.5",
         "@babel/helper-environment-visitor": "^7.22.5",
@@ -185,8 +193,8 @@
         "@babel/helper-optimise-call-expression": "^7.22.5",
         "@babel/helper-replace-supers": "^7.22.5",
         "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5",
-        "@babel/helper-split-export-declaration": "^7.22.5",
-        "semver": "^6.3.0"
+        "@babel/helper-split-export-declaration": "^7.22.6",
+        "@nicolo-ribaudo/semver-v6": "^6.3.3"
       },
       "engines": {
         "node": ">=6.9.0"
@@ -196,13 +204,13 @@
       }
     },
     "node_modules/@babel/helper-create-regexp-features-plugin": {
-      "version": "7.22.5",
-      "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.5.tgz",
-      "integrity": "sha512-1VpEFOIbMRaXyDeUwUfmTIxExLwQ+zkW+Bh5zXpApA3oQedBx9v/updixWxnx/bZpKw7u8VxWjb/qWpIcmPq8A==",
+      "version": "7.22.6",
+      "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.6.tgz",
+      "integrity": "sha512-nBookhLKxAWo/TUCmhnaEJyLz2dekjQvv5SRpE9epWQBcpedWLKt8aZdsuT9XV5ovzR3fENLjRXVT0GsSlGGhA==",
       "dependencies": {
         "@babel/helper-annotate-as-pure": "^7.22.5",
-        "regexpu-core": "^5.3.1",
-        "semver": "^6.3.0"
+        "@nicolo-ribaudo/semver-v6": "^6.3.3",
+        "regexpu-core": "^5.3.1"
       },
       "engines": {
         "node": ">=6.9.0"
@@ -212,16 +220,15 @@
       }
     },
     "node_modules/@babel/helper-define-polyfill-provider": {
-      "version": "0.4.0",
-      "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.0.tgz",
-      "integrity": "sha512-RnanLx5ETe6aybRi1cO/edaRH+bNYWaryCEmjDDYyNr4wnSzyOp8T0dWipmqVHKEY3AbVKUom50AKSlj1zmKbg==",
+      "version": "0.4.1",
+      "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.1.tgz",
+      "integrity": "sha512-kX4oXixDxG197yhX+J3Wp+NpL2wuCFjWQAr6yX2jtCnflK9ulMI51ULFGIrWiX1jGfvAxdHp+XQCcP2bZGPs9A==",
       "dependencies": {
-        "@babel/helper-compilation-targets": "^7.17.7",
-        "@babel/helper-plugin-utils": "^7.16.7",
+        "@babel/helper-compilation-targets": "^7.22.6",
+        "@babel/helper-plugin-utils": "^7.22.5",
         "debug": "^4.1.1",
         "lodash.debounce": "^4.0.8",
-        "resolve": "^1.14.2",
-        "semver": "^6.1.2"
+        "resolve": "^1.14.2"
       },
       "peerDependencies": {
         "@babel/core": "^7.4.0-0"
@@ -373,9 +380,9 @@
       }
     },
     "node_modules/@babel/helper-split-export-declaration": {
-      "version": "7.22.5",
-      "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.5.tgz",
-      "integrity": "sha512-thqK5QFghPKWLhAV321lxF95yCg2K3Ob5yw+M3VHWfdia0IkPXUtoLH8x/6Fh486QUvzhb8YOWHChTVen2/PoQ==",
+      "version": "7.22.6",
+      "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz",
+      "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==",
       "dependencies": {
         "@babel/types": "^7.22.5"
       },
@@ -422,12 +429,12 @@
       }
     },
     "node_modules/@babel/helpers": {
-      "version": "7.22.5",
-      "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.5.tgz",
-      "integrity": "sha512-pSXRmfE1vzcUIDFQcSGA5Mr+GxBV9oiRKDuDxXvWQQBCh8HoIjs/2DlDB7H8smac1IVrB9/xdXj2N3Wol9Cr+Q==",
+      "version": "7.22.6",
+      "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.6.tgz",
+      "integrity": "sha512-YjDs6y/fVOYFV8hAf1rxd1QvR9wJe1pDBZ2AREKq/SDayfPzgk0PBnVuTCE5X1acEpMMNOVUqoe+OwiZGJ+OaA==",
       "dependencies": {
         "@babel/template": "^7.22.5",
-        "@babel/traverse": "^7.22.5",
+        "@babel/traverse": "^7.22.6",
         "@babel/types": "^7.22.5"
       },
       "engines": {
@@ -448,9 +455,9 @@
       }
     },
     "node_modules/@babel/parser": {
-      "version": "7.22.5",
-      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.5.tgz",
-      "integrity": "sha512-DFZMC9LJUG9PLOclRC32G63UXwzqS2koQC8dkx+PLdmt1xSePYpbT/NbsrJy8Q/muXz7o/h/d4A7Fuyixm559Q==",
+      "version": "7.22.7",
+      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.7.tgz",
+      "integrity": "sha512-7NF8pOkHP5o2vpmGgNGcfAeCvOYhGLyA3Z4eBQkT1RJlWu47n63bCs93QfJ2hIAFCil7L5P2IWhs1oToVgrL0Q==",
       "bin": {
         "parser": "bin/babel-parser.js"
       },
@@ -746,9 +753,9 @@
       }
     },
     "node_modules/@babel/plugin-transform-async-generator-functions": {
-      "version": "7.22.5",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.22.5.tgz",
-      "integrity": "sha512-gGOEvFzm3fWoyD5uZq7vVTD57pPJ3PczPUD/xCFGjzBpUosnklmXyKnGQbbbGs1NPNPskFex0j93yKbHt0cHyg==",
+      "version": "7.22.7",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.22.7.tgz",
+      "integrity": "sha512-7HmE7pk/Fmke45TODvxvkxRMV9RazV+ZZzhOL9AG8G29TLrr3jkjwF7uJfxZ30EoXpO+LJkq4oA8NjO2DTnEDg==",
       "dependencies": {
         "@babel/helper-environment-visitor": "^7.22.5",
         "@babel/helper-plugin-utils": "^7.22.5",
@@ -838,18 +845,18 @@
       }
     },
     "node_modules/@babel/plugin-transform-classes": {
-      "version": "7.22.5",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.22.5.tgz",
-      "integrity": "sha512-2edQhLfibpWpsVBx2n/GKOz6JdGQvLruZQfGr9l1qes2KQaWswjBzhQF7UDUZMNaMMQeYnQzxwOMPsbYF7wqPQ==",
+      "version": "7.22.6",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.22.6.tgz",
+      "integrity": "sha512-58EgM6nuPNG6Py4Z3zSuu0xWu2VfodiMi72Jt5Kj2FECmaYk1RrTXA45z6KBFsu9tRgwQDwIiY4FXTt+YsSFAQ==",
       "dependencies": {
         "@babel/helper-annotate-as-pure": "^7.22.5",
-        "@babel/helper-compilation-targets": "^7.22.5",
+        "@babel/helper-compilation-targets": "^7.22.6",
         "@babel/helper-environment-visitor": "^7.22.5",
         "@babel/helper-function-name": "^7.22.5",
         "@babel/helper-optimise-call-expression": "^7.22.5",
         "@babel/helper-plugin-utils": "^7.22.5",
         "@babel/helper-replace-supers": "^7.22.5",
-        "@babel/helper-split-export-declaration": "^7.22.5",
+        "@babel/helper-split-export-declaration": "^7.22.6",
         "globals": "^11.1.0"
       },
       "engines": {
@@ -1221,9 +1228,9 @@
       }
     },
     "node_modules/@babel/plugin-transform-optional-chaining": {
-      "version": "7.22.5",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.22.5.tgz",
-      "integrity": "sha512-AconbMKOMkyG+xCng2JogMCDcqW8wedQAqpVIL4cOSescZ7+iW8utC6YDZLMCSUIReEA733gzRSaOSXMAt/4WQ==",
+      "version": "7.22.6",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.22.6.tgz",
+      "integrity": "sha512-Vd5HiWml0mDVtcLHIoEU5sw6HOUW/Zk0acLs/SAeuLzkGNOPc9DB4nkUajemhCmTIz3eiaKREZn2hQQqF79YTg==",
       "dependencies": {
         "@babel/helper-plugin-utils": "^7.22.5",
         "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5",
@@ -1456,12 +1463,12 @@
       }
     },
     "node_modules/@babel/preset-env": {
-      "version": "7.22.5",
-      "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.22.5.tgz",
-      "integrity": "sha512-fj06hw89dpiZzGZtxn+QybifF07nNiZjZ7sazs2aVDcysAZVGjW7+7iFYxg6GLNM47R/thYfLdrXc+2f11Vi9A==",
+      "version": "7.22.7",
+      "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.22.7.tgz",
+      "integrity": "sha512-1whfDtW+CzhETuzYXfcgZAh8/GFMeEbz0V5dVgya8YeJyCU6Y/P2Gnx4Qb3MylK68Zu9UiwUvbPMPTpFAOJ+sQ==",
       "dependencies": {
-        "@babel/compat-data": "^7.22.5",
-        "@babel/helper-compilation-targets": "^7.22.5",
+        "@babel/compat-data": "^7.22.6",
+        "@babel/helper-compilation-targets": "^7.22.6",
         "@babel/helper-plugin-utils": "^7.22.5",
         "@babel/helper-validator-option": "^7.22.5",
         "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.22.5",
@@ -1486,13 +1493,13 @@
         "@babel/plugin-syntax-top-level-await": "^7.14.5",
         "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6",
         "@babel/plugin-transform-arrow-functions": "^7.22.5",
-        "@babel/plugin-transform-async-generator-functions": "^7.22.5",
+        "@babel/plugin-transform-async-generator-functions": "^7.22.7",
         "@babel/plugin-transform-async-to-generator": "^7.22.5",
         "@babel/plugin-transform-block-scoped-functions": "^7.22.5",
         "@babel/plugin-transform-block-scoping": "^7.22.5",
         "@babel/plugin-transform-class-properties": "^7.22.5",
         "@babel/plugin-transform-class-static-block": "^7.22.5",
-        "@babel/plugin-transform-classes": "^7.22.5",
+        "@babel/plugin-transform-classes": "^7.22.6",
         "@babel/plugin-transform-computed-properties": "^7.22.5",
         "@babel/plugin-transform-destructuring": "^7.22.5",
         "@babel/plugin-transform-dotall-regex": "^7.22.5",
@@ -1517,7 +1524,7 @@
         "@babel/plugin-transform-object-rest-spread": "^7.22.5",
         "@babel/plugin-transform-object-super": "^7.22.5",
         "@babel/plugin-transform-optional-catch-binding": "^7.22.5",
-        "@babel/plugin-transform-optional-chaining": "^7.22.5",
+        "@babel/plugin-transform-optional-chaining": "^7.22.6",
         "@babel/plugin-transform-parameters": "^7.22.5",
         "@babel/plugin-transform-private-methods": "^7.22.5",
         "@babel/plugin-transform-private-property-in-object": "^7.22.5",
@@ -1535,11 +1542,11 @@
         "@babel/plugin-transform-unicode-sets-regex": "^7.22.5",
         "@babel/preset-modules": "^0.1.5",
         "@babel/types": "^7.22.5",
-        "babel-plugin-polyfill-corejs2": "^0.4.3",
-        "babel-plugin-polyfill-corejs3": "^0.8.1",
-        "babel-plugin-polyfill-regenerator": "^0.5.0",
-        "core-js-compat": "^3.30.2",
-        "semver": "^6.3.0"
+        "@nicolo-ribaudo/semver-v6": "^6.3.3",
+        "babel-plugin-polyfill-corejs2": "^0.4.4",
+        "babel-plugin-polyfill-corejs3": "^0.8.2",
+        "babel-plugin-polyfill-regenerator": "^0.5.1",
+        "core-js-compat": "^3.31.0"
       },
       "engines": {
         "node": ">=6.9.0"
@@ -1569,9 +1576,9 @@
       "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA=="
     },
     "node_modules/@babel/runtime": {
-      "version": "7.22.5",
-      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.5.tgz",
-      "integrity": "sha512-ecjvYlnAaZ/KVneE/OdKYBYfgXV3Ptu6zQWmgEF7vwKhQnvVS6bjMD2XYgj+SNvQ1GfK/pjgokfPkC/2CO8CuA==",
+      "version": "7.22.6",
+      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.6.tgz",
+      "integrity": "sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ==",
       "dependencies": {
         "regenerator-runtime": "^0.13.11"
       },
@@ -1593,17 +1600,17 @@
       }
     },
     "node_modules/@babel/traverse": {
-      "version": "7.22.5",
-      "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.5.tgz",
-      "integrity": "sha512-7DuIjPgERaNo6r+PZwItpjCZEa5vyw4eJGufeLxrPdBXBoLcCJCIasvK6pK/9DVNrLZTLFhUGqaC6X/PA007TQ==",
+      "version": "7.22.8",
+      "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.8.tgz",
+      "integrity": "sha512-y6LPR+wpM2I3qJrsheCTwhIinzkETbplIgPBbwvqPKc+uljeA5gP+3nP8irdYt1mjQaDnlIcG+dw8OjAco4GXw==",
       "dependencies": {
         "@babel/code-frame": "^7.22.5",
-        "@babel/generator": "^7.22.5",
+        "@babel/generator": "^7.22.7",
         "@babel/helper-environment-visitor": "^7.22.5",
         "@babel/helper-function-name": "^7.22.5",
         "@babel/helper-hoist-variables": "^7.22.5",
-        "@babel/helper-split-export-declaration": "^7.22.5",
-        "@babel/parser": "^7.22.5",
+        "@babel/helper-split-export-declaration": "^7.22.6",
+        "@babel/parser": "^7.22.7",
         "@babel/types": "^7.22.5",
         "debug": "^4.1.0",
         "globals": "^11.1.0"
@@ -1659,9 +1666,9 @@
       }
     },
     "node_modules/@codemirror/lint": {
-      "version": "6.3.0",
-      "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.3.0.tgz",
-      "integrity": "sha512-tzxOVQNoDhhwFNfcTO2IB74wQoWarARcH6gv3YufPpiJ9yhcb7zD6JCkO5+FWARskqRFc8GFa6E+wUyOvADl5A==",
+      "version": "6.4.0",
+      "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.4.0.tgz",
+      "integrity": "sha512-6VZ44Ysh/Zn07xrGkdtNfmHCbGSHZzFBdzWi0pbd7chAQ/iUcpLGX99NYRZTa7Ugqg4kEHCqiHhcZnH0gLIgSg==",
       "dependencies": {
         "@codemirror/state": "^6.0.0",
         "@codemirror/view": "^6.0.0",
@@ -1685,9 +1692,9 @@
       }
     },
     "node_modules/@codemirror/view": {
-      "version": "6.14.0",
-      "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.14.0.tgz",
-      "integrity": "sha512-I263FPs4In42MNmrdwN2DfmYPFMVMXgT7o/mxdGp4jv5LPs8i0FOxzmxF5yeeQdYSTztb2ZhmPIu0ahveInVTg==",
+      "version": "6.14.1",
+      "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.14.1.tgz",
+      "integrity": "sha512-ofcsI7lRFo4N0rfnd+V3Gh2boQU3DmaaSKhDOvXUWjeOeuupMXer2e/3i9TUFN7aEIntv300EFBWPEiYVm2svg==",
       "dependencies": {
         "@codemirror/state": "^6.1.4",
         "style-mod": "^4.0.0",
@@ -1695,9 +1702,9 @@
       }
     },
     "node_modules/@esbuild/android-arm": {
-      "version": "0.17.19",
-      "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.19.tgz",
-      "integrity": "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==",
+      "version": "0.18.11",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.11.tgz",
+      "integrity": "sha512-q4qlUf5ucwbUJZXF5tEQ8LF7y0Nk4P58hOsGk3ucY0oCwgQqAnqXVbUuahCddVHfrxmpyewRpiTHwVHIETYu7Q==",
       "cpu": [
         "arm"
       ],
@@ -1710,9 +1717,9 @@
       }
     },
     "node_modules/@esbuild/android-arm64": {
-      "version": "0.17.19",
-      "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz",
-      "integrity": "sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==",
+      "version": "0.18.11",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.11.tgz",
+      "integrity": "sha512-snieiq75Z1z5LJX9cduSAjUr7vEI1OdlzFPMw0HH5YI7qQHDd3qs+WZoMrWYDsfRJSq36lIA6mfZBkvL46KoIw==",
       "cpu": [
         "arm64"
       ],
@@ -1725,9 +1732,9 @@
       }
     },
     "node_modules/@esbuild/android-x64": {
-      "version": "0.17.19",
-      "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.19.tgz",
-      "integrity": "sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==",
+      "version": "0.18.11",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.11.tgz",
+      "integrity": "sha512-iPuoxQEV34+hTF6FT7om+Qwziv1U519lEOvekXO9zaMMlT9+XneAhKL32DW3H7okrCOBQ44BMihE8dclbZtTuw==",
       "cpu": [
         "x64"
       ],
@@ -1740,9 +1747,9 @@
       }
     },
     "node_modules/@esbuild/darwin-arm64": {
-      "version": "0.17.19",
-      "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz",
-      "integrity": "sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==",
+      "version": "0.18.11",
+      "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.11.tgz",
+      "integrity": "sha512-Gm0QkI3k402OpfMKyQEEMG0RuW2LQsSmI6OeO4El2ojJMoF5NLYb3qMIjvbG/lbMeLOGiW6ooU8xqc+S0fgz2w==",
       "cpu": [
         "arm64"
       ],
@@ -1755,9 +1762,9 @@
       }
     },
     "node_modules/@esbuild/darwin-x64": {
-      "version": "0.17.19",
-      "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz",
-      "integrity": "sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==",
+      "version": "0.18.11",
+      "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.11.tgz",
+      "integrity": "sha512-N15Vzy0YNHu6cfyDOjiyfJlRJCB/ngKOAvoBf1qybG3eOq0SL2Lutzz9N7DYUbb7Q23XtHPn6lMDF6uWbGv9Fw==",
       "cpu": [
         "x64"
       ],
@@ -1770,9 +1777,9 @@
       }
     },
     "node_modules/@esbuild/freebsd-arm64": {
-      "version": "0.17.19",
-      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz",
-      "integrity": "sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==",
+      "version": "0.18.11",
+      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.11.tgz",
+      "integrity": "sha512-atEyuq6a3omEY5qAh5jIORWk8MzFnCpSTUruBgeyN9jZq1K/QI9uke0ATi3MHu4L8c59CnIi4+1jDKMuqmR71A==",
       "cpu": [
         "arm64"
       ],
@@ -1785,9 +1792,9 @@
       }
     },
     "node_modules/@esbuild/freebsd-x64": {
-      "version": "0.17.19",
-      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz",
-      "integrity": "sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==",
+      "version": "0.18.11",
+      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.11.tgz",
+      "integrity": "sha512-XtuPrEfBj/YYYnAAB7KcorzzpGTvOr/dTtXPGesRfmflqhA4LMF0Gh/n5+a9JBzPuJ+CGk17CA++Hmr1F/gI0Q==",
       "cpu": [
         "x64"
       ],
@@ -1800,9 +1807,9 @@
       }
     },
     "node_modules/@esbuild/linux-arm": {
-      "version": "0.17.19",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz",
-      "integrity": "sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==",
+      "version": "0.18.11",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.11.tgz",
+      "integrity": "sha512-Idipz+Taso/toi2ETugShXjQ3S59b6m62KmLHkJlSq/cBejixmIydqrtM2XTvNCywFl3VC7SreSf6NV0i6sRyg==",
       "cpu": [
         "arm"
       ],
@@ -1815,9 +1822,9 @@
       }
     },
     "node_modules/@esbuild/linux-arm64": {
-      "version": "0.17.19",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz",
-      "integrity": "sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==",
+      "version": "0.18.11",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.11.tgz",
+      "integrity": "sha512-c6Vh2WS9VFKxKZ2TvJdA7gdy0n6eSy+yunBvv4aqNCEhSWVor1TU43wNRp2YLO9Vng2G+W94aRz+ILDSwAiYog==",
       "cpu": [
         "arm64"
       ],
@@ -1830,9 +1837,9 @@
       }
     },
     "node_modules/@esbuild/linux-ia32": {
-      "version": "0.17.19",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz",
-      "integrity": "sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==",
+      "version": "0.18.11",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.11.tgz",
+      "integrity": "sha512-S3hkIF6KUqRh9n1Q0dSyYcWmcVa9Cg+mSoZEfFuzoYXXsk6196qndrM+ZiHNwpZKi3XOXpShZZ+9dfN5ykqjjw==",
       "cpu": [
         "ia32"
       ],
@@ -1845,9 +1852,9 @@
       }
     },
     "node_modules/@esbuild/linux-loong64": {
-      "version": "0.17.19",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz",
-      "integrity": "sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==",
+      "version": "0.18.11",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.11.tgz",
+      "integrity": "sha512-MRESANOoObQINBA+RMZW+Z0TJWpibtE7cPFnahzyQHDCA9X9LOmGh68MVimZlM9J8n5Ia8lU773te6O3ILW8kw==",
       "cpu": [
         "loong64"
       ],
@@ -1860,9 +1867,9 @@
       }
     },
     "node_modules/@esbuild/linux-mips64el": {
-      "version": "0.17.19",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz",
-      "integrity": "sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==",
+      "version": "0.18.11",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.11.tgz",
+      "integrity": "sha512-qVyPIZrXNMOLYegtD1u8EBccCrBVshxMrn5MkuFc3mEVsw7CCQHaqZ4jm9hbn4gWY95XFnb7i4SsT3eflxZsUg==",
       "cpu": [
         "mips64el"
       ],
@@ -1875,9 +1882,9 @@
       }
     },
     "node_modules/@esbuild/linux-ppc64": {
-      "version": "0.17.19",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz",
-      "integrity": "sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==",
+      "version": "0.18.11",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.11.tgz",
+      "integrity": "sha512-T3yd8vJXfPirZaUOoA9D2ZjxZX4Gr3QuC3GztBJA6PklLotc/7sXTOuuRkhE9W/5JvJP/K9b99ayPNAD+R+4qQ==",
       "cpu": [
         "ppc64"
       ],
@@ -1890,9 +1897,9 @@
       }
     },
     "node_modules/@esbuild/linux-riscv64": {
-      "version": "0.17.19",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz",
-      "integrity": "sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==",
+      "version": "0.18.11",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.11.tgz",
+      "integrity": "sha512-evUoRPWiwuFk++snjH9e2cAjF5VVSTj+Dnf+rkO/Q20tRqv+644279TZlPK8nUGunjPAtQRCj1jQkDAvL6rm2w==",
       "cpu": [
         "riscv64"
       ],
@@ -1905,9 +1912,9 @@
       }
     },
     "node_modules/@esbuild/linux-s390x": {
-      "version": "0.17.19",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz",
-      "integrity": "sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==",
+      "version": "0.18.11",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.11.tgz",
+      "integrity": "sha512-/SlRJ15XR6i93gRWquRxYCfhTeC5PdqEapKoLbX63PLCmAkXZHY2uQm2l9bN0oPHBsOw2IswRZctMYS0MijFcg==",
       "cpu": [
         "s390x"
       ],
@@ -1920,9 +1927,9 @@
       }
     },
     "node_modules/@esbuild/linux-x64": {
-      "version": "0.17.19",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz",
-      "integrity": "sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==",
+      "version": "0.18.11",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.11.tgz",
+      "integrity": "sha512-xcncej+wF16WEmIwPtCHi0qmx1FweBqgsRtEL1mSHLFR6/mb3GEZfLQnx+pUDfRDEM4DQF8dpXIW7eDOZl1IbA==",
       "cpu": [
         "x64"
       ],
@@ -1935,9 +1942,9 @@
       }
     },
     "node_modules/@esbuild/netbsd-x64": {
-      "version": "0.17.19",
-      "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz",
-      "integrity": "sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==",
+      "version": "0.18.11",
+      "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.11.tgz",
+      "integrity": "sha512-aSjMHj/F7BuS1CptSXNg6S3M4F3bLp5wfFPIJM+Km2NfIVfFKhdmfHF9frhiCLIGVzDziggqWll0B+9AUbud/Q==",
       "cpu": [
         "x64"
       ],
@@ -1950,9 +1957,9 @@
       }
     },
     "node_modules/@esbuild/openbsd-x64": {
-      "version": "0.17.19",
-      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz",
-      "integrity": "sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==",
+      "version": "0.18.11",
+      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.11.tgz",
+      "integrity": "sha512-tNBq+6XIBZtht0xJGv7IBB5XaSyvYPCm1PxJ33zLQONdZoLVM0bgGqUrXnJyiEguD9LU4AHiu+GCXy/Hm9LsdQ==",
       "cpu": [
         "x64"
       ],
@@ -1965,9 +1972,9 @@
       }
     },
     "node_modules/@esbuild/sunos-x64": {
-      "version": "0.17.19",
-      "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz",
-      "integrity": "sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==",
+      "version": "0.18.11",
+      "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.11.tgz",
+      "integrity": "sha512-kxfbDOrH4dHuAAOhr7D7EqaYf+W45LsAOOhAet99EyuxxQmjbk8M9N4ezHcEiCYPaiW8Dj3K26Z2V17Gt6p3ng==",
       "cpu": [
         "x64"
       ],
@@ -1980,9 +1987,9 @@
       }
     },
     "node_modules/@esbuild/win32-arm64": {
-      "version": "0.17.19",
-      "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz",
-      "integrity": "sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==",
+      "version": "0.18.11",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.11.tgz",
+      "integrity": "sha512-Sh0dDRyk1Xi348idbal7lZyfSkjhJsdFeuC13zqdipsvMetlGiFQNdO+Yfp6f6B4FbyQm7qsk16yaZk25LChzg==",
       "cpu": [
         "arm64"
       ],
@@ -1995,9 +2002,9 @@
       }
     },
     "node_modules/@esbuild/win32-ia32": {
-      "version": "0.17.19",
-      "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz",
-      "integrity": "sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==",
+      "version": "0.18.11",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.11.tgz",
+      "integrity": "sha512-o9JUIKF1j0rqJTFbIoF4bXj6rvrTZYOrfRcGyL0Vm5uJ/j5CkBD/51tpdxe9lXEDouhRgdr/BYzUrDOvrWwJpg==",
       "cpu": [
         "ia32"
       ],
@@ -2010,9 +2017,9 @@
       }
     },
     "node_modules/@esbuild/win32-x64": {
-      "version": "0.17.19",
-      "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz",
-      "integrity": "sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==",
+      "version": "0.18.11",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.11.tgz",
+      "integrity": "sha512-rQI4cjLHd2hGsM1LqgDI7oOCYbQ6IBOVsX9ejuRMSze0GqXUG2ekwiKkiBU1pRGSeCqFFHxTrcEydB2Hyoz9CA==",
       "cpu": [
         "x64"
       ],
@@ -2047,13 +2054,13 @@
       }
     },
     "node_modules/@eslint/eslintrc": {
-      "version": "2.0.3",
-      "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.3.tgz",
-      "integrity": "sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ==",
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.0.tgz",
+      "integrity": "sha512-Lj7DECXqIVCqnqjjHMPna4vn6GJcMgul/wuS0je9OZ9gsL0zzDpKPVtcG1HaDVc+9y+qgXneTeUMbCqXJNpH1A==",
       "dependencies": {
         "ajv": "^6.12.4",
         "debug": "^4.3.2",
-        "espree": "^9.5.2",
+        "espree": "^9.6.0",
         "globals": "^13.19.0",
         "ignore": "^5.2.0",
         "import-fresh": "^3.2.1",
@@ -2083,9 +2090,9 @@
       }
     },
     "node_modules/@eslint/js": {
-      "version": "8.43.0",
-      "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.43.0.tgz",
-      "integrity": "sha512-s2UHCoiXfxMvmfzqoN+vrQ84ahUSYde9qNO1MdxmoEhyHWsfmwOpFlwYV+ePJEVc7gFnATGUi376WowX1N7tFg==",
+      "version": "8.44.0",
+      "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.44.0.tgz",
+      "integrity": "sha512-Ag+9YM4ocKQx9AarydN0KY2j0ErMHNIocPDrVo8zAE44xLTjEtz81OdR68/cydGtk6m6jDb5Za3r2useMzYmSw==",
       "engines": {
         "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
       }
@@ -2263,6 +2270,17 @@
         "node": ">= 14"
       }
     },
+    "node_modules/@jest/schemas": {
+      "version": "29.6.0",
+      "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.0.tgz",
+      "integrity": "sha512-rxLjXyJBTL4LQeJW3aKo0M/+GkCOXsO+8i9Iu7eDb6KwtP65ayoDsitrdPBtujxQ88k4wI2FNYfa6TOGwSn6cQ==",
+      "dependencies": {
+        "@sinclair/typebox": "^0.27.8"
+      },
+      "engines": {
+        "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+      }
+    },
     "node_modules/@jridgewell/gen-mapping": {
       "version": "0.3.3",
       "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz",
@@ -2293,9 +2311,9 @@
       }
     },
     "node_modules/@jridgewell/source-map": {
-      "version": "0.3.3",
-      "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.3.tgz",
-      "integrity": "sha512-b+fsZXeLYi9fEULmfBrhxn4IrPlINf8fiNarzTof004v3lFdntdwa9PF7vFJqm3mg7s+ScJMxXaE3Acp1irZcg==",
+      "version": "0.3.5",
+      "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz",
+      "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==",
       "peer": true,
       "dependencies": {
         "@jridgewell/gen-mapping": "^0.3.0",
@@ -2335,22 +2353,30 @@
       }
     },
     "node_modules/@lezer/json": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/@lezer/json/-/json-1.0.0.tgz",
-      "integrity": "sha512-zbAuUY09RBzCoCA3lJ1+ypKw5WSNvLqGMtasdW6HvVOqZoCpPr8eWrsGnOVWGKGn8Rh21FnrKRVlJXrGAVUqRw==",
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/@lezer/json/-/json-1.0.1.tgz",
+      "integrity": "sha512-nkVC27qiEZEjySbi6gQRuMwa2sDu2PtfjSgz0A4QF81QyRGm3kb2YRzLcOPcTEtmcwvrX/cej7mlhbwViA4WJw==",
       "dependencies": {
         "@lezer/highlight": "^1.0.0",
         "@lezer/lr": "^1.0.0"
       }
     },
     "node_modules/@lezer/lr": {
-      "version": "1.3.7",
-      "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.3.7.tgz",
-      "integrity": "sha512-ssHKb3p0MxhJXT2i7UBmgAY1BIM3Uq/D772Qutu3EVmxWIyNMU12nQ0rL3Fhu+MiFtiTzyTmd3xGwEf3ON5PSA==",
+      "version": "1.3.9",
+      "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.3.9.tgz",
+      "integrity": "sha512-XPz6dzuTHlnsbA5M2DZgjflNQ+9Hi5Swhic0RULdp3oOs3rh6bqGZolosVqN/fQIT8uNiepzINJDnS39oweTHQ==",
       "dependencies": {
         "@lezer/common": "^1.0.0"
       }
     },
+    "node_modules/@nicolo-ribaudo/semver-v6": {
+      "version": "6.3.3",
+      "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/semver-v6/-/semver-v6-6.3.3.tgz",
+      "integrity": "sha512-3Yc1fUTs69MG/uZbJlLSI3JISMn2UV2rg+1D/vROUqZyh3l6iYHCs7GMp+M40ZD7yOdDbYjJcU1oTJhrc+dGKg==",
+      "bin": {
+        "semver": "bin/semver.js"
+      }
+    },
     "node_modules/@nodelib/fs.scandir": {
       "version": "2.1.5",
       "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -2383,6 +2409,53 @@
         "node": ">= 8"
       }
     },
+    "node_modules/@pkgr/utils": {
+      "version": "2.4.2",
+      "resolved": "https://registry.npmjs.org/@pkgr/utils/-/utils-2.4.2.tgz",
+      "integrity": "sha512-POgTXhjrTfbTV63DiFXav4lBHiICLKKwDeaKn9Nphwj7WH6m0hMMCaJkMyRWjgtPFyRKRVoMXXjczsTQRDEhYw==",
+      "dependencies": {
+        "cross-spawn": "^7.0.3",
+        "fast-glob": "^3.3.0",
+        "is-glob": "^4.0.3",
+        "open": "^9.1.0",
+        "picocolors": "^1.0.0",
+        "tslib": "^2.6.0"
+      },
+      "engines": {
+        "node": "^12.20.0 || ^14.18.0 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/unts"
+      }
+    },
+    "node_modules/@pkgr/utils/node_modules/define-lazy-prop": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz",
+      "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==",
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/@pkgr/utils/node_modules/open": {
+      "version": "9.1.0",
+      "resolved": "https://registry.npmjs.org/open/-/open-9.1.0.tgz",
+      "integrity": "sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg==",
+      "dependencies": {
+        "default-browser": "^4.0.0",
+        "define-lazy-prop": "^3.0.0",
+        "is-inside-container": "^1.0.0",
+        "is-wsl": "^2.2.0"
+      },
+      "engines": {
+        "node": ">=14.16"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/@playwright/test": {
       "version": "1.35.1",
       "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.35.1.tgz",
@@ -2445,6 +2518,11 @@
       "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz",
       "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ=="
     },
+    "node_modules/@sinclair/typebox": {
+      "version": "0.27.8",
+      "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
+      "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA=="
+    },
     "node_modules/@toml-tools/lexer": {
       "version": "0.3.1",
       "resolved": "https://registry.npmjs.org/@toml-tools/lexer/-/lexer-0.3.1.tgz",
@@ -2521,20 +2599,20 @@
       "integrity": "sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA=="
     },
     "node_modules/@types/node": {
-      "version": "20.3.2",
-      "resolved": "https://registry.npmjs.org/@types/node/-/node-20.3.2.tgz",
-      "integrity": "sha512-vOBLVQeCQfIcF/2Y7eKFTqrMnizK5lRNQ7ykML/5RuwVXVWxYkgwS7xbt4B6fKCUPgbSL5FSsjHQpaGQP/dQmw=="
+      "version": "20.4.1",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.1.tgz",
+      "integrity": "sha512-JIzsAvJeA/5iY6Y/OxZbv1lUcc8dNSE77lb2gnBH+/PJ3lFR1Ccvgwl5JWnHAkNHcRsT0TbpVOsiMKZ1F/yyJg=="
     },
     "node_modules/@vitejs/plugin-legacy": {
-      "version": "4.0.5",
-      "resolved": "https://registry.npmjs.org/@vitejs/plugin-legacy/-/plugin-legacy-4.0.5.tgz",
-      "integrity": "sha512-0N1CSAHjFTaxcKC9IcKDWMapmAfGn2mNdbROMf6UW0kypYLzDK8vAAbJIFW0jWvkv2aZfTgNs4KmQxoQDupUuQ==",
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/@vitejs/plugin-legacy/-/plugin-legacy-4.1.0.tgz",
+      "integrity": "sha512-bLPHaKAKtPq40Cx2Hauz/VE39kStx7gUC21RM+0/OvBgiOlqrK3b1mIMUc4Cak1xLOrK99j174/7xczfu4OLHA==",
       "dependencies": {
-        "@babel/core": "^7.22.5",
-        "@babel/preset-env": "^7.22.5",
+        "@babel/core": "^7.22.6",
+        "@babel/preset-env": "^7.22.6",
         "browserslist": "^4.21.9",
         "core-js": "^3.31.0",
-        "magic-string": "^0.30.0",
+        "magic-string": "^0.30.1",
         "regenerator-runtime": "^0.13.11",
         "systemjs": "^6.14.1"
       },
@@ -2562,12 +2640,12 @@
       }
     },
     "node_modules/@vitest/expect": {
-      "version": "0.32.2",
-      "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-0.32.2.tgz",
-      "integrity": "sha512-6q5yzweLnyEv5Zz1fqK5u5E83LU+gOMVBDuxBl2d2Jfx1BAp5M+rZgc5mlyqdnxquyoiOXpXmFNkcGcfFnFH3Q==",
+      "version": "0.33.0",
+      "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-0.33.0.tgz",
+      "integrity": "sha512-sVNf+Gla3mhTCxNJx+wJLDPp/WcstOe0Ksqz4Vec51MmgMth/ia0MGFEkIZmVGeTL5HtjYR4Wl/ZxBxBXZJTzQ==",
       "dependencies": {
-        "@vitest/spy": "0.32.2",
-        "@vitest/utils": "0.32.2",
+        "@vitest/spy": "0.33.0",
+        "@vitest/utils": "0.33.0",
         "chai": "^4.3.7"
       },
       "funding": {
@@ -2575,14 +2653,13 @@
       }
     },
     "node_modules/@vitest/runner": {
-      "version": "0.32.2",
-      "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-0.32.2.tgz",
-      "integrity": "sha512-06vEL0C1pomOEktGoLjzZw+1Fb+7RBRhmw/06WkDrd1akkT9i12su0ku+R/0QM69dfkIL/rAIDTG+CSuQVDcKw==",
+      "version": "0.33.0",
+      "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-0.33.0.tgz",
+      "integrity": "sha512-UPfACnmCB6HKRHTlcgCoBh6ppl6fDn+J/xR8dTufWiKt/74Y9bHci5CKB8tESSV82zKYtkBJo9whU3mNvfaisg==",
       "dependencies": {
-        "@vitest/utils": "0.32.2",
-        "concordance": "^5.0.4",
+        "@vitest/utils": "0.33.0",
         "p-limit": "^4.0.0",
-        "pathe": "^1.1.0"
+        "pathe": "^1.1.1"
       },
       "funding": {
         "url": "https://opencollective.com/vitest"
@@ -2619,13 +2696,13 @@
       }
     },
     "node_modules/@vitest/snapshot": {
-      "version": "0.32.2",
-      "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-0.32.2.tgz",
-      "integrity": "sha512-JwhpeH/PPc7GJX38vEfCy9LtRzf9F4er7i4OsAJyV7sjPwjj+AIR8cUgpMTWK4S3TiamzopcTyLsZDMuldoi5A==",
+      "version": "0.33.0",
+      "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-0.33.0.tgz",
+      "integrity": "sha512-tJjrl//qAHbyHajpFvr8Wsk8DIOODEebTu7pgBrP07iOepR5jYkLFiqLq2Ltxv+r0uptUb4izv1J8XBOwKkVYA==",
       "dependencies": {
-        "magic-string": "^0.30.0",
-        "pathe": "^1.1.0",
-        "pretty-format": "^27.5.1"
+        "magic-string": "^0.30.1",
+        "pathe": "^1.1.1",
+        "pretty-format": "^29.5.0"
       },
       "funding": {
         "url": "https://opencollective.com/vitest"
@@ -2637,24 +2714,24 @@
       "integrity": "sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q=="
     },
     "node_modules/@vitest/spy": {
-      "version": "0.32.2",
-      "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-0.32.2.tgz",
-      "integrity": "sha512-Q/ZNILJ4ca/VzQbRM8ur3Si5Sardsh1HofatG9wsJY1RfEaw0XKP8IVax2lI1qnrk9YPuG9LA2LkZ0EI/3d4ug==",
+      "version": "0.33.0",
+      "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-0.33.0.tgz",
+      "integrity": "sha512-Kv+yZ4hnH1WdiAkPUQTpRxW8kGtH8VRTnus7ZTGovFYM1ZezJpvGtb9nPIjPnptHbsyIAxYZsEpVPYgtpjGnrg==",
       "dependencies": {
-        "tinyspy": "^2.1.0"
+        "tinyspy": "^2.1.1"
       },
       "funding": {
         "url": "https://opencollective.com/vitest"
       }
     },
     "node_modules/@vitest/utils": {
-      "version": "0.32.2",
-      "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-0.32.2.tgz",
-      "integrity": "sha512-lnJ0T5i03j0IJaeW73hxe2AuVnZ/y1BhhCOuIcl9LIzXnbpXJT9Lrt6brwKHXLOiA7MZ6N5hSJjt0xE1dGNCzQ==",
+      "version": "0.33.0",
+      "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-0.33.0.tgz",
+      "integrity": "sha512-pF1w22ic965sv+EN6uoePkAOTkAPWM03Ri/jXNyMIKBb/XHLDPfhLvf/Fa9g0YECevAIz56oVYXhodLvLQ/awA==",
       "dependencies": {
         "diff-sequences": "^29.4.3",
         "loupe": "^2.3.6",
-        "pretty-format": "^27.5.1"
+        "pretty-format": "^29.5.0"
       },
       "funding": {
         "url": "https://opencollective.com/vitest"
@@ -2800,9 +2877,9 @@
       "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
     },
     "node_modules/acorn": {
-      "version": "8.9.0",
-      "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.9.0.tgz",
-      "integrity": "sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ==",
+      "version": "8.10.0",
+      "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz",
+      "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==",
       "bin": {
         "acorn": "bin/acorn"
       },
@@ -2996,36 +3073,36 @@
       }
     },
     "node_modules/babel-plugin-polyfill-corejs2": {
-      "version": "0.4.3",
-      "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.3.tgz",
-      "integrity": "sha512-bM3gHc337Dta490gg+/AseNB9L4YLHxq1nGKZZSHbhXv4aTYU2MD2cjza1Ru4S6975YLTaL1K8uJf6ukJhhmtw==",
+      "version": "0.4.4",
+      "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.4.tgz",
+      "integrity": "sha512-9WeK9snM1BfxB38goUEv2FLnA6ja07UMfazFHzCXUb3NyDZAwfXvQiURQ6guTTMeHcOsdknULm1PDhs4uWtKyA==",
       "dependencies": {
-        "@babel/compat-data": "^7.17.7",
-        "@babel/helper-define-polyfill-provider": "^0.4.0",
-        "semver": "^6.1.1"
+        "@babel/compat-data": "^7.22.6",
+        "@babel/helper-define-polyfill-provider": "^0.4.1",
+        "@nicolo-ribaudo/semver-v6": "^6.3.3"
       },
       "peerDependencies": {
         "@babel/core": "^7.0.0-0"
       }
     },
     "node_modules/babel-plugin-polyfill-corejs3": {
-      "version": "0.8.1",
-      "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.1.tgz",
-      "integrity": "sha512-ikFrZITKg1xH6pLND8zT14UPgjKHiGLqex7rGEZCH2EvhsneJaJPemmpQaIZV5AL03II+lXylw3UmddDK8RU5Q==",
+      "version": "0.8.2",
+      "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.2.tgz",
+      "integrity": "sha512-Cid+Jv1BrY9ReW9lIfNlNpsI53N+FN7gE+f73zLAUbr9C52W4gKLWSByx47pfDJsEysojKArqOtOKZSVIIUTuQ==",
       "dependencies": {
-        "@babel/helper-define-polyfill-provider": "^0.4.0",
-        "core-js-compat": "^3.30.1"
+        "@babel/helper-define-polyfill-provider": "^0.4.1",
+        "core-js-compat": "^3.31.0"
       },
       "peerDependencies": {
         "@babel/core": "^7.0.0-0"
       }
     },
     "node_modules/babel-plugin-polyfill-regenerator": {
-      "version": "0.5.0",
-      "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.0.tgz",
-      "integrity": "sha512-hDJtKjMLVa7Z+LwnTCxoDLQj6wdc+B8dun7ayF2fYieI6OzfuvcLMB32ihJZ4UhCBwNYGl5bg/x/P9cMdnkc2g==",
+      "version": "0.5.1",
+      "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.1.tgz",
+      "integrity": "sha512-L8OyySuI6OSQ5hFy9O+7zFjyr4WhAfRjLIOkhQGYl+emwJkd/S4XXT1JpfrgR1jrQ1NcGiOh+yAdGlF8pnC3Jw==",
       "dependencies": {
-        "@babel/helper-define-polyfill-provider": "^0.4.0"
+        "@babel/helper-define-polyfill-provider": "^0.4.1"
       },
       "peerDependencies": {
         "@babel/core": "^7.0.0-0"
@@ -3036,6 +3113,14 @@
       "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
       "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
     },
+    "node_modules/big-integer": {
+      "version": "1.6.51",
+      "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz",
+      "integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==",
+      "engines": {
+        "node": ">=0.6"
+      }
+    },
     "node_modules/binary-extensions": {
       "version": "2.2.0",
       "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
@@ -3052,11 +3137,6 @@
         "url": "https://github.com/sponsors/antfu"
       }
     },
-    "node_modules/blueimp-md5": {
-      "version": "2.19.0",
-      "resolved": "https://registry.npmjs.org/blueimp-md5/-/blueimp-md5-2.19.0.tgz",
-      "integrity": "sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w=="
-    },
     "node_modules/boolbase": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
@@ -3080,6 +3160,17 @@
         "@popperjs/core": "^2.11.7"
       }
     },
+    "node_modules/bplist-parser": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.2.0.tgz",
+      "integrity": "sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw==",
+      "dependencies": {
+        "big-integer": "^1.6.44"
+      },
+      "engines": {
+        "node": ">= 5.10.0"
+      }
+    },
     "node_modules/brace-expansion": {
       "version": "1.1.11",
       "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@@ -3136,6 +3227,20 @@
       "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
       "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="
     },
+    "node_modules/bundle-name": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-3.0.0.tgz",
+      "integrity": "sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==",
+      "dependencies": {
+        "run-applescript": "^5.0.0"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/cac": {
       "version": "6.7.14",
       "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
@@ -3174,9 +3279,9 @@
       }
     },
     "node_modules/caniuse-lite": {
-      "version": "1.0.30001508",
-      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001508.tgz",
-      "integrity": "sha512-sdQZOJdmt3GJs1UMNpCCCyeuS2IEGLXnHyAo9yIO5JJDjbjoVRij4M1qep6P6gFpptD1PqIYgzM+gwJbOi92mw==",
+      "version": "1.0.30001514",
+      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001514.tgz",
+      "integrity": "sha512-ENcIpYBmwAAOm/V2cXgM7rZUrKKaqisZl4ZAI520FIkqGXUxJjmaIssbRW5HVVR5tyV6ygTLIm15aU8LUmQSaQ==",
       "funding": [
         {
           "type": "opencollective",
@@ -3349,54 +3454,6 @@
       "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
       "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
     },
-    "node_modules/concordance": {
-      "version": "5.0.4",
-      "resolved": "https://registry.npmjs.org/concordance/-/concordance-5.0.4.tgz",
-      "integrity": "sha512-OAcsnTEYu1ARJqWVGwf4zh4JDfHZEaSNlNccFmt8YjB2l/n19/PF2viLINHc57vO4FKIAFl2FWASIGZZWZ2Kxw==",
-      "dependencies": {
-        "date-time": "^3.1.0",
-        "esutils": "^2.0.3",
-        "fast-diff": "^1.2.0",
-        "js-string-escape": "^1.0.1",
-        "lodash": "^4.17.15",
-        "md5-hex": "^3.0.1",
-        "semver": "^7.3.2",
-        "well-known-symbols": "^2.0.0"
-      },
-      "engines": {
-        "node": ">=10.18.0 <11 || >=12.14.0 <13 || >=14"
-      }
-    },
-    "node_modules/concordance/node_modules/lru-cache": {
-      "version": "6.0.0",
-      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
-      "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
-      "dependencies": {
-        "yallist": "^4.0.0"
-      },
-      "engines": {
-        "node": ">=10"
-      }
-    },
-    "node_modules/concordance/node_modules/semver": {
-      "version": "7.5.3",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz",
-      "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==",
-      "dependencies": {
-        "lru-cache": "^6.0.0"
-      },
-      "bin": {
-        "semver": "bin/semver.js"
-      },
-      "engines": {
-        "node": ">=10"
-      }
-    },
-    "node_modules/concordance/node_modules/yallist": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
-      "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
-    },
     "node_modules/config-chain": {
       "version": "1.1.13",
       "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz",
@@ -3449,9 +3506,9 @@
       "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A=="
     },
     "node_modules/core-js": {
-      "version": "3.31.0",
-      "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.31.0.tgz",
-      "integrity": "sha512-NIp2TQSGfR6ba5aalZD+ZQ1fSxGhDo/s1w0nx3RYzf2pnJxt7YynxFlFScP6eV7+GZsKO95NSjGxyJsU3DZgeQ==",
+      "version": "3.31.1",
+      "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.31.1.tgz",
+      "integrity": "sha512-2sKLtfq1eFST7l7v62zaqXacPc7uG8ZAya8ogijLhTtaKNcpzpB4TMoTw2Si+8GYKRwFPMMtUT0263QFWFfqyQ==",
       "hasInstallScript": true,
       "funding": {
         "type": "opencollective",
@@ -3459,11 +3516,11 @@
       }
     },
     "node_modules/core-js-compat": {
-      "version": "3.31.0",
-      "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.31.0.tgz",
-      "integrity": "sha512-hM7YCu1cU6Opx7MXNu0NuumM0ezNeAeRKadixyiQELWY3vT3De9S4J5ZBMraWV2vZnrE1Cirl0GtFtDtMUXzPw==",
+      "version": "3.31.1",
+      "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.31.1.tgz",
+      "integrity": "sha512-wIDWd2s5/5aJSdpOJHfSibxNODxoGoWOBHt8JSPB41NOE94M7kuTPZCYLOlTtuoXTsBPKobpJ6T+y0SSy5L9SA==",
       "dependencies": {
-        "browserslist": "^4.21.5"
+        "browserslist": "^4.21.9"
       },
       "funding": {
         "type": "opencollective",
@@ -3471,9 +3528,9 @@
       }
     },
     "node_modules/countup.js": {
-      "version": "2.6.2",
-      "resolved": "https://registry.npmjs.org/countup.js/-/countup.js-2.6.2.tgz",
-      "integrity": "sha512-PRvoilkebwr1MKaRAllyJl2cD7mgunGZWXLaSFL+YqHXlJpjLX7SHTWonRfIx2xg1Xf7SPbo92bOOonSIomRYQ=="
+      "version": "2.7.0",
+      "resolved": "https://registry.npmjs.org/countup.js/-/countup.js-2.7.0.tgz",
+      "integrity": "sha512-IP9nYLGgW//0If73eXQdFlReGhpFGHaStqB1v82FknxnUWueF6HFuuOXySW4sEDMc88PsZL1EOn/NPkfTZalmQ=="
     },
     "node_modules/crelt": {
       "version": "1.0.6",
@@ -3548,17 +3605,6 @@
         "node": ">=12"
       }
     },
-    "node_modules/date-time": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/date-time/-/date-time-3.1.0.tgz",
-      "integrity": "sha512-uqCUKXE5q1PNBXjPqvwhwJf9SwMoAHBgWJ6DcrnS5o+W2JOiIILl0JEdVD8SGujrNS02GGxgwAg2PN2zONgtjg==",
-      "dependencies": {
-        "time-zone": "^1.0.0"
-      },
-      "engines": {
-        "node": ">=6"
-      }
-    },
     "node_modules/debug": {
       "version": "4.3.4",
       "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
@@ -3596,6 +3642,38 @@
       "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
       "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="
     },
+    "node_modules/default-browser": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-4.0.0.tgz",
+      "integrity": "sha512-wX5pXO1+BrhMkSbROFsyxUm0i/cJEScyNhA4PPxc41ICuv05ZZB/MX28s8aZx6xjmatvebIapF6hLEKEcpneUA==",
+      "dependencies": {
+        "bundle-name": "^3.0.0",
+        "default-browser-id": "^3.0.0",
+        "execa": "^7.1.1",
+        "titleize": "^3.0.0"
+      },
+      "engines": {
+        "node": ">=14.16"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/default-browser-id": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-3.0.0.tgz",
+      "integrity": "sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA==",
+      "dependencies": {
+        "bplist-parser": "^0.2.0",
+        "untildify": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/define-lazy-prop": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz",
@@ -3729,9 +3807,9 @@
       "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
     },
     "node_modules/electron-to-chromium": {
-      "version": "1.4.442",
-      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.442.tgz",
-      "integrity": "sha512-RkrZF//Ya+0aJq2NM3OdisNh5ZodZq1rdXOS96G8DdDgpDKqKE81yTbbQ3F/4CKm1JBPsGu1Lp/akkna2xO06Q=="
+      "version": "1.4.454",
+      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.454.tgz",
+      "integrity": "sha512-pmf1rbAStw8UEQ0sr2cdJtWl48ZMuPD9Sto8HVQOq9vx9j2WgDEN6lYoaqFvqEHYOmGA9oRGn7LqWI9ta0YugQ=="
     },
     "node_modules/emoji-regex": {
       "version": "8.0.0",
@@ -3842,9 +3920,9 @@
       }
     },
     "node_modules/esbuild": {
-      "version": "0.17.19",
-      "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz",
-      "integrity": "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==",
+      "version": "0.18.11",
+      "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.11.tgz",
+      "integrity": "sha512-i8u6mQF0JKJUlGR3OdFLKldJQMMs8OqM9Cc3UCi9XXziJ9WERM5bfkHaEAy0YAvPRMgqSW55W7xYn84XtEFTtA==",
       "hasInstallScript": true,
       "bin": {
         "esbuild": "bin/esbuild"
@@ -3853,28 +3931,28 @@
         "node": ">=12"
       },
       "optionalDependencies": {
-        "@esbuild/android-arm": "0.17.19",
-        "@esbuild/android-arm64": "0.17.19",
-        "@esbuild/android-x64": "0.17.19",
-        "@esbuild/darwin-arm64": "0.17.19",
-        "@esbuild/darwin-x64": "0.17.19",
-        "@esbuild/freebsd-arm64": "0.17.19",
-        "@esbuild/freebsd-x64": "0.17.19",
-        "@esbuild/linux-arm": "0.17.19",
-        "@esbuild/linux-arm64": "0.17.19",
-        "@esbuild/linux-ia32": "0.17.19",
-        "@esbuild/linux-loong64": "0.17.19",
-        "@esbuild/linux-mips64el": "0.17.19",
-        "@esbuild/linux-ppc64": "0.17.19",
-        "@esbuild/linux-riscv64": "0.17.19",
-        "@esbuild/linux-s390x": "0.17.19",
-        "@esbuild/linux-x64": "0.17.19",
-        "@esbuild/netbsd-x64": "0.17.19",
-        "@esbuild/openbsd-x64": "0.17.19",
-        "@esbuild/sunos-x64": "0.17.19",
-        "@esbuild/win32-arm64": "0.17.19",
-        "@esbuild/win32-ia32": "0.17.19",
-        "@esbuild/win32-x64": "0.17.19"
+        "@esbuild/android-arm": "0.18.11",
+        "@esbuild/android-arm64": "0.18.11",
+        "@esbuild/android-x64": "0.18.11",
+        "@esbuild/darwin-arm64": "0.18.11",
+        "@esbuild/darwin-x64": "0.18.11",
+        "@esbuild/freebsd-arm64": "0.18.11",
+        "@esbuild/freebsd-x64": "0.18.11",
+        "@esbuild/linux-arm": "0.18.11",
+        "@esbuild/linux-arm64": "0.18.11",
+        "@esbuild/linux-ia32": "0.18.11",
+        "@esbuild/linux-loong64": "0.18.11",
+        "@esbuild/linux-mips64el": "0.18.11",
+        "@esbuild/linux-ppc64": "0.18.11",
+        "@esbuild/linux-riscv64": "0.18.11",
+        "@esbuild/linux-s390x": "0.18.11",
+        "@esbuild/linux-x64": "0.18.11",
+        "@esbuild/netbsd-x64": "0.18.11",
+        "@esbuild/openbsd-x64": "0.18.11",
+        "@esbuild/sunos-x64": "0.18.11",
+        "@esbuild/win32-arm64": "0.18.11",
+        "@esbuild/win32-ia32": "0.18.11",
+        "@esbuild/win32-x64": "0.18.11"
       }
     },
     "node_modules/escalade": {
@@ -3899,14 +3977,13 @@
       }
     },
     "node_modules/escodegen": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz",
-      "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==",
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz",
+      "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==",
       "dependencies": {
         "esprima": "^4.0.1",
         "estraverse": "^5.2.0",
-        "esutils": "^2.0.2",
-        "optionator": "^0.8.1"
+        "esutils": "^2.0.2"
       },
       "bin": {
         "escodegen": "bin/escodegen.js",
@@ -3919,62 +3996,15 @@
         "source-map": "~0.6.1"
       }
     },
-    "node_modules/escodegen/node_modules/levn": {
-      "version": "0.3.0",
-      "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
-      "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==",
-      "dependencies": {
-        "prelude-ls": "~1.1.2",
-        "type-check": "~0.3.2"
-      },
-      "engines": {
-        "node": ">= 0.8.0"
-      }
-    },
-    "node_modules/escodegen/node_modules/optionator": {
-      "version": "0.8.3",
-      "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz",
-      "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==",
-      "dependencies": {
-        "deep-is": "~0.1.3",
-        "fast-levenshtein": "~2.0.6",
-        "levn": "~0.3.0",
-        "prelude-ls": "~1.1.2",
-        "type-check": "~0.3.2",
-        "word-wrap": "~1.2.3"
-      },
-      "engines": {
-        "node": ">= 0.8.0"
-      }
-    },
-    "node_modules/escodegen/node_modules/prelude-ls": {
-      "version": "1.1.2",
-      "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
-      "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==",
-      "engines": {
-        "node": ">= 0.8.0"
-      }
-    },
-    "node_modules/escodegen/node_modules/type-check": {
-      "version": "0.3.2",
-      "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
-      "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==",
-      "dependencies": {
-        "prelude-ls": "~1.1.2"
-      },
-      "engines": {
-        "node": ">= 0.8.0"
-      }
-    },
     "node_modules/eslint": {
-      "version": "8.43.0",
-      "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.43.0.tgz",
-      "integrity": "sha512-aaCpf2JqqKesMFGgmRPessmVKjcGXqdlAYLLC3THM8t5nBRZRQ+st5WM/hoJXkdioEXLLbXgclUpM0TXo5HX5Q==",
+      "version": "8.44.0",
+      "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.44.0.tgz",
+      "integrity": "sha512-0wpHoUbDUHgNCyvFB5aXLiQVfK9B0at6gUvzy83k4kAsQ/u769TQDX6iKC+aO4upIHO9WSaA3QoXYQDHbNwf1A==",
       "dependencies": {
         "@eslint-community/eslint-utils": "^4.2.0",
         "@eslint-community/regexpp": "^4.4.0",
-        "@eslint/eslintrc": "^2.0.3",
-        "@eslint/js": "8.43.0",
+        "@eslint/eslintrc": "^2.1.0",
+        "@eslint/js": "8.44.0",
         "@humanwhocodes/config-array": "^0.11.10",
         "@humanwhocodes/module-importer": "^1.0.1",
         "@nodelib/fs.walk": "^1.2.8",
@@ -3986,7 +4016,7 @@
         "escape-string-regexp": "^4.0.0",
         "eslint-scope": "^7.2.0",
         "eslint-visitor-keys": "^3.4.1",
-        "espree": "^9.5.2",
+        "espree": "^9.6.0",
         "esquery": "^1.4.2",
         "esutils": "^2.0.2",
         "fast-deep-equal": "^3.1.3",
@@ -4006,7 +4036,7 @@
         "lodash.merge": "^4.6.2",
         "minimatch": "^3.1.2",
         "natural-compare": "^1.4.0",
-        "optionator": "^0.9.1",
+        "optionator": "^0.9.3",
         "strip-ansi": "^6.0.1",
         "strip-json-comments": "^3.1.0",
         "text-table": "^0.2.0"
@@ -4159,20 +4189,28 @@
       }
     },
     "node_modules/eslint-plugin-prettier": {
-      "version": "4.2.1",
-      "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz",
-      "integrity": "sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==",
+      "version": "5.0.0-alpha.2",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.0.0-alpha.2.tgz",
+      "integrity": "sha512-F6YBCbrRzvZwcINw3crm1+/uX/i+rJYaFErPtwCfUoPLywRfY7pwBtI3yMe5OpIotuaiws8cd29oM80ca6NQSQ==",
       "dependencies": {
-        "prettier-linter-helpers": "^1.0.0"
+        "prettier-linter-helpers": "^1.0.0",
+        "synckit": "^0.8.5"
       },
       "engines": {
-        "node": ">=12.0.0"
+        "node": "^14.18.0 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/prettier"
       },
       "peerDependencies": {
-        "eslint": ">=7.28.0",
-        "prettier": ">=2.0.0"
+        "@types/eslint": ">=8.0.0",
+        "eslint": ">=8.0.0",
+        "prettier": ">=3.0.0"
       },
       "peerDependenciesMeta": {
+        "@types/eslint": {
+          "optional": true
+        },
         "eslint-config-prettier": {
           "optional": true
         }
@@ -4221,9 +4259,9 @@
       }
     },
     "node_modules/eslint-plugin-vue/node_modules/semver": {
-      "version": "7.5.3",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz",
-      "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==",
+      "version": "7.5.4",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
+      "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
       "dependencies": {
         "lru-cache": "^6.0.0"
       },
@@ -4388,11 +4426,11 @@
       }
     },
     "node_modules/espree": {
-      "version": "9.5.2",
-      "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.2.tgz",
-      "integrity": "sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==",
+      "version": "9.6.0",
+      "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.0.tgz",
+      "integrity": "sha512-1FH/IiruXZ84tpUlm0aCUEwMl2Ho5ilqVh0VvQXw+byAz/4SAciyHLlfmL5WYqsvD38oymdUwBss0LtK8m4s/A==",
       "dependencies": {
-        "acorn": "^8.8.0",
+        "acorn": "^8.9.0",
         "acorn-jsx": "^5.3.2",
         "eslint-visitor-keys": "^3.4.1"
       },
@@ -4458,6 +4496,28 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/execa": {
+      "version": "7.1.1",
+      "resolved": "https://registry.npmjs.org/execa/-/execa-7.1.1.tgz",
+      "integrity": "sha512-wH0eMf/UXckdUYnO21+HDztteVv05rq2GXksxT4fCGeHkBhw1DROXh40wcjMcRqDOWE7iPJ4n3M7e2+YFP+76Q==",
+      "dependencies": {
+        "cross-spawn": "^7.0.3",
+        "get-stream": "^6.0.1",
+        "human-signals": "^4.3.0",
+        "is-stream": "^3.0.0",
+        "merge-stream": "^2.0.0",
+        "npm-run-path": "^5.1.0",
+        "onetime": "^6.0.0",
+        "signal-exit": "^3.0.7",
+        "strip-final-newline": "^3.0.0"
+      },
+      "engines": {
+        "node": "^14.18.0 || ^16.14.0 || >=18.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/sindresorhus/execa?sponsor=1"
+      }
+    },
     "node_modules/extend-shallow": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
@@ -4480,9 +4540,9 @@
       "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw=="
     },
     "node_modules/fast-glob": {
-      "version": "3.2.12",
-      "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz",
-      "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==",
+      "version": "3.3.0",
+      "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.0.tgz",
+      "integrity": "sha512-ChDuvbOypPuNjO8yIDf36x7BlZX1smcUMTTcyoIjycexOxd6DFsKsg21qVBzEmr3G7fUKIRy2/psii+CIUt7FA==",
       "dependencies": {
         "@nodelib/fs.stat": "^2.0.2",
         "@nodelib/fs.walk": "^1.2.3",
@@ -4740,6 +4800,17 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/get-stream": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
+      "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/get-symbol-description": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz",
@@ -4826,13 +4897,13 @@
       }
     },
     "node_modules/globby": {
-      "version": "13.2.0",
-      "resolved": "https://registry.npmjs.org/globby/-/globby-13.2.0.tgz",
-      "integrity": "sha512-jWsQfayf13NvqKUIL3Ta+CIqMnvlaIDFveWE/dpOZ9+3AMEJozsxDvKA02zync9UuvOM8rOXzsD5GqKP4OnWPQ==",
+      "version": "13.2.2",
+      "resolved": "https://registry.npmjs.org/globby/-/globby-13.2.2.tgz",
+      "integrity": "sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==",
       "dependencies": {
         "dir-glob": "^3.0.1",
-        "fast-glob": "^3.2.11",
-        "ignore": "^5.2.0",
+        "fast-glob": "^3.3.0",
+        "ignore": "^5.2.4",
         "merge2": "^1.4.1",
         "slash": "^4.0.0"
       },
@@ -4899,9 +4970,9 @@
       }
     },
     "node_modules/happy-dom": {
-      "version": "9.20.3",
-      "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-9.20.3.tgz",
-      "integrity": "sha512-eBsgauT435fXFvQDNcmm5QbGtYzxEzOaX35Ia+h6yP/wwa4xSWZh1CfP+mGby8Hk6Xu59mTkpyf72rUXHNxY7A==",
+      "version": "10.0.3",
+      "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-10.0.3.tgz",
+      "integrity": "sha512-WkCP+Z5fX6U5PY+yHP3ElV5D9PoxRAHRWPFq3pG9rg/6Hjf5ak7dozAgSCywsTRUq2qfa8vV8OQvUy5pRXy8EQ==",
       "dependencies": {
         "css.escape": "^1.5.1",
         "entities": "^4.5.0",
@@ -5074,6 +5145,14 @@
         "node": ">= 6"
       }
     },
+    "node_modules/human-signals": {
+      "version": "4.3.1",
+      "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz",
+      "integrity": "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==",
+      "engines": {
+        "node": ">=14.18.0"
+      }
+    },
     "node_modules/iconv-lite": {
       "version": "0.6.3",
       "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
@@ -5283,6 +5362,37 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/is-inside-container": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz",
+      "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==",
+      "dependencies": {
+        "is-docker": "^3.0.0"
+      },
+      "bin": {
+        "is-inside-container": "cli.js"
+      },
+      "engines": {
+        "node": ">=14.16"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/is-inside-container/node_modules/is-docker": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz",
+      "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==",
+      "bin": {
+        "is-docker": "cli.js"
+      },
+      "engines": {
+        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/is-negative-zero": {
       "version": "2.0.2",
       "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz",
@@ -5355,6 +5465,17 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/is-stream": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz",
+      "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==",
+      "engines": {
+        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/is-string": {
       "version": "1.0.7",
       "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz",
@@ -5429,9 +5550,9 @@
       "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="
     },
     "node_modules/jiti": {
-      "version": "1.18.2",
-      "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.18.2.tgz",
-      "integrity": "sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg==",
+      "version": "1.19.1",
+      "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.19.1.tgz",
+      "integrity": "sha512-oVhqoRDaBXf7sjkll95LHVS6Myyyb1zaunVwk4Z0+WPSW4gjS0pl01zYKHScTuyEhQsFxV5L4DR5r+YqSyqyyg==",
       "bin": {
         "jiti": "bin/jiti.js"
       }
@@ -5467,14 +5588,6 @@
         "node": ">=10"
       }
     },
-    "node_modules/js-string-escape": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/js-string-escape/-/js-string-escape-1.0.1.tgz",
-      "integrity": "sha512-Smw4xcfIQ5LVjAOuJCvN/zIodzA/BBSsluuoSykP+lUvScIi4U6RJLfwHet5cxFnCswUjISV8oAXaqaJDY3chg==",
-      "engines": {
-        "node": ">= 0.8"
-      }
-    },
     "node_modules/js-tokens": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -5697,11 +5810,11 @@
       }
     },
     "node_modules/magic-string": {
-      "version": "0.30.0",
-      "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.0.tgz",
-      "integrity": "sha512-LA+31JYDJLs82r2ScLrlz1GjSgu66ZV518eyWT+S8VhyQn/JL0u9MeBOvQMGYiPk1DBiSN9DDMOcXvigJZaViQ==",
+      "version": "0.30.1",
+      "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.1.tgz",
+      "integrity": "sha512-mbVKXPmS0z0G4XqFDCTllmDQ6coZzn94aMlb0o/A4HEHJCKcanlDZwYJgwnkmgD3jyWhUgj9VsPrfd972yPffA==",
       "dependencies": {
-        "@jridgewell/sourcemap-codec": "^1.4.13"
+        "@jridgewell/sourcemap-codec": "^1.4.15"
       },
       "engines": {
         "node": ">=12"
@@ -5768,22 +5881,16 @@
         "markty": "0.0.4"
       }
     },
-    "node_modules/md5-hex": {
-      "version": "3.0.1",
-      "resolved": "https://registry.npmjs.org/md5-hex/-/md5-hex-3.0.1.tgz",
-      "integrity": "sha512-BUiRtTtV39LIJwinWBjqVsU9xhdnz7/i889V859IBFpuqGAj6LuOvHv5XLbgZ2R7ptJoJaEcxkv88/h25T7Ciw==",
-      "dependencies": {
-        "blueimp-md5": "^2.10.0"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
     "node_modules/mdurl": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz",
       "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g=="
     },
+    "node_modules/merge-stream": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
+      "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="
+    },
     "node_modules/merge2": {
       "version": "1.4.1",
       "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
@@ -5823,6 +5930,17 @@
         "node": ">= 0.6"
       }
     },
+    "node_modules/mimic-fn": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz",
+      "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==",
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/minimatch": {
       "version": "3.1.2",
       "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
@@ -5911,9 +6029,9 @@
       }
     },
     "node_modules/node-releases": {
-      "version": "2.0.12",
-      "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.12.tgz",
-      "integrity": "sha512-QzsYKWhXTWx8h1kIvqfnC++o0pEmpRQA/aenALsL2F4pqNVr7YzcdMlDij5WBnwftRbJCNJL/O7zdKaxKPHqgQ=="
+      "version": "2.0.13",
+      "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz",
+      "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ=="
     },
     "node_modules/nopt": {
       "version": "6.0.0",
@@ -5937,6 +6055,31 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/npm-run-path": {
+      "version": "5.1.0",
+      "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz",
+      "integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==",
+      "dependencies": {
+        "path-key": "^4.0.0"
+      },
+      "engines": {
+        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/npm-run-path/node_modules/path-key": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz",
+      "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==",
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/nth-check": {
       "version": "2.1.1",
       "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
@@ -5949,9 +6092,9 @@
       }
     },
     "node_modules/nwsapi": {
-      "version": "2.2.5",
-      "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.5.tgz",
-      "integrity": "sha512-6xpotnECFy/og7tKSBVmUNft7J3jyXAka4XvG6AUhFWRz+Q/Ljus7znJAA3bxColfQLdS+XsjoodtJfCgeTEFQ=="
+      "version": "2.2.7",
+      "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.7.tgz",
+      "integrity": "sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ=="
     },
     "node_modules/object-inspect": {
       "version": "1.12.3",
@@ -6021,6 +6164,20 @@
         "wrappy": "1"
       }
     },
+    "node_modules/onetime": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz",
+      "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==",
+      "dependencies": {
+        "mimic-fn": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/open": {
       "version": "8.4.2",
       "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz",
@@ -6038,16 +6195,16 @@
       }
     },
     "node_modules/optionator": {
-      "version": "0.9.1",
-      "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",
-      "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==",
+      "version": "0.9.3",
+      "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz",
+      "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==",
       "dependencies": {
+        "@aashutoshrathi/word-wrap": "^1.2.3",
         "deep-is": "^0.1.3",
         "fast-levenshtein": "^2.0.6",
         "levn": "^0.4.1",
         "prelude-ls": "^1.2.1",
-        "type-check": "^0.4.0",
-        "word-wrap": "^1.2.3"
+        "type-check": "^0.4.0"
       },
       "engines": {
         "node": ">= 0.8.0"
@@ -6231,9 +6388,9 @@
       }
     },
     "node_modules/postcss": {
-      "version": "8.4.24",
-      "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.24.tgz",
-      "integrity": "sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg==",
+      "version": "8.4.25",
+      "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.25.tgz",
+      "integrity": "sha512-7taJ/8t2av0Z+sQEvNzCkpDynl0tX3uJMCODi6nT3PfASC7dYCWV9aQ+uiCf+KBD4SEFcu+GvJdGdwzQ6OSjCw==",
       "funding": [
         {
           "type": "opencollective",
@@ -6278,14 +6435,14 @@
       }
     },
     "node_modules/prettier": {
-      "version": "2.8.8",
-      "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz",
-      "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==",
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.0.tgz",
+      "integrity": "sha512-zBf5eHpwHOGPC47h0zrPyNn+eAEIdEzfywMoYn2XPi0P44Zp0tSq64rq0xAREh4auw2cJZHo9QUob+NqCQky4g==",
       "bin": {
-        "prettier": "bin-prettier.js"
+        "prettier": "bin/prettier.cjs"
       },
       "engines": {
-        "node": ">=10.13.0"
+        "node": ">=14"
       },
       "funding": {
         "url": "https://github.com/prettier/prettier?sponsor=1"
@@ -6323,16 +6480,16 @@
       }
     },
     "node_modules/pretty-format": {
-      "version": "27.5.1",
-      "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz",
-      "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
+      "version": "29.6.1",
+      "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.1.tgz",
+      "integrity": "sha512-7jRj+yXO0W7e4/tSJKoR7HRIHLPPjtNaUGG2xxKQnGvPNRkgWcQ0AZX6P4KBRJN4FcTBWb3sa7DVUJmocYuoog==",
       "dependencies": {
-        "ansi-regex": "^5.0.1",
+        "@jest/schemas": "^29.6.0",
         "ansi-styles": "^5.0.0",
-        "react-is": "^17.0.1"
+        "react-is": "^18.0.0"
       },
       "engines": {
-        "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0"
+        "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
       }
     },
     "node_modules/pretty-format/node_modules/ansi-styles": {
@@ -6399,9 +6556,9 @@
       ]
     },
     "node_modules/react-is": {
-      "version": "17.0.2",
-      "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
-      "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
+      "version": "18.2.0",
+      "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
+      "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w=="
     },
     "node_modules/readdirp": {
       "version": "3.6.0",
@@ -6603,9 +6760,9 @@
       }
     },
     "node_modules/rollup": {
-      "version": "3.25.3",
-      "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.25.3.tgz",
-      "integrity": "sha512-ZT279hx8gszBj9uy5FfhoG4bZx8c+0A1sbqtr7Q3KNWIizpTdDEPZbV2xcbvHsnFp4MavCQYZyzApJ+virB8Yw==",
+      "version": "3.26.2",
+      "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.26.2.tgz",
+      "integrity": "sha512-6umBIGVz93er97pMgQO08LuH3m6PUb3jlDUUGFsNJB6VgTCUaDFpupf5JfU30529m/UKOgmiX+uY6Sx8cOYpLA==",
       "bin": {
         "rollup": "dist/bin/rollup"
       },
@@ -6650,6 +6807,102 @@
         "node": ">= 8"
       }
     },
+    "node_modules/run-applescript": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-5.0.0.tgz",
+      "integrity": "sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg==",
+      "dependencies": {
+        "execa": "^5.0.0"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/run-applescript/node_modules/execa": {
+      "version": "5.1.1",
+      "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
+      "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==",
+      "dependencies": {
+        "cross-spawn": "^7.0.3",
+        "get-stream": "^6.0.0",
+        "human-signals": "^2.1.0",
+        "is-stream": "^2.0.0",
+        "merge-stream": "^2.0.0",
+        "npm-run-path": "^4.0.1",
+        "onetime": "^5.1.2",
+        "signal-exit": "^3.0.3",
+        "strip-final-newline": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sindresorhus/execa?sponsor=1"
+      }
+    },
+    "node_modules/run-applescript/node_modules/human-signals": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
+      "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==",
+      "engines": {
+        "node": ">=10.17.0"
+      }
+    },
+    "node_modules/run-applescript/node_modules/is-stream": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
+      "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
+      "engines": {
+        "node": ">=8"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/run-applescript/node_modules/mimic-fn": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
+      "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/run-applescript/node_modules/npm-run-path": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
+      "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==",
+      "dependencies": {
+        "path-key": "^3.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/run-applescript/node_modules/onetime": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
+      "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
+      "dependencies": {
+        "mimic-fn": "^2.1.0"
+      },
+      "engines": {
+        "node": ">=6"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/run-applescript/node_modules/strip-final-newline": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
+      "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==",
+      "engines": {
+        "node": ">=6"
+      }
+    },
     "node_modules/run-parallel": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
@@ -6805,6 +7058,11 @@
       "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz",
       "integrity": "sha512-fCvEXfh6NWpm+YSuY2bpXb/VIihqWA6hLsgboC+0nl71Q7N7o2eaCW8mJa/NLvQhs6jpd3VZV4UiUQlV6+lc8g=="
     },
+    "node_modules/signal-exit": {
+      "version": "3.0.7",
+      "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
+      "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="
+    },
     "node_modules/sirv": {
       "version": "2.0.3",
       "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.3.tgz",
@@ -6973,6 +7231,17 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/strip-final-newline": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz",
+      "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==",
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/strip-json-comments": {
       "version": "3.1.1",
       "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
@@ -7027,6 +7296,21 @@
       "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
       "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw=="
     },
+    "node_modules/synckit": {
+      "version": "0.8.5",
+      "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.5.tgz",
+      "integrity": "sha512-L1dapNV6vu2s/4Sputv8xGsCdAVlb5nRDMFU/E27D44l5U6cw1g0dGd45uLc+OXjNMmF4ntiMdCimzcjFKQI8Q==",
+      "dependencies": {
+        "@pkgr/utils": "^2.3.1",
+        "tslib": "^2.5.0"
+      },
+      "engines": {
+        "node": "^14.18.0 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/unts"
+      }
+    },
     "node_modules/systemjs": {
       "version": "6.14.1",
       "resolved": "https://registry.npmjs.org/systemjs/-/systemjs-6.14.1.tgz",
@@ -7055,23 +7339,15 @@
       "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
       "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw=="
     },
-    "node_modules/time-zone": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/time-zone/-/time-zone-1.0.0.tgz",
-      "integrity": "sha512-TIsDdtKo6+XrPtiTm1ssmMngN1sAhyKnTO2kunQWqNPWIVvCm15Wmw4SWInwTVgJ5u/Tr04+8Ei9TNcw4x4ONA==",
-      "engines": {
-        "node": ">=4"
-      }
-    },
     "node_modules/tinybench": {
       "version": "2.5.0",
       "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.5.0.tgz",
       "integrity": "sha512-kRwSG8Zx4tjF9ZiyH4bhaebu+EDz1BOx9hOigYHlUW4xxI/wKIUQUqo018UlU4ar6ATPBsaMrdbKZ+tmPdohFA=="
     },
     "node_modules/tinypool": {
-      "version": "0.5.0",
-      "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.5.0.tgz",
-      "integrity": "sha512-paHQtnrlS1QZYKF/GnLoOM/DN9fqaGOFbCbxzAhwniySnzl9Ebk8w73/dd34DAhe/obUbPAOldTyYXQZxnPBPQ==",
+      "version": "0.6.0",
+      "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.6.0.tgz",
+      "integrity": "sha512-FdswUUo5SxRizcBc6b1GSuLpLjisa8N8qMyYoP3rl+bym+QauhtJP5bvZY1ytt8krKGmMLYIRl36HBZfeAoqhQ==",
       "engines": {
         "node": ">=14.0.0"
       }
@@ -7084,6 +7360,17 @@
         "node": ">=14.0.0"
       }
     },
+    "node_modules/titleize": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/titleize/-/titleize-3.0.0.tgz",
+      "integrity": "sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==",
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/to-fast-properties": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
@@ -7304,6 +7591,14 @@
         "node": ">= 0.8"
       }
     },
+    "node_modules/untildify": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz",
+      "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==",
+      "engines": {
+        "node": ">=8"
+      }
+    },
     "node_modules/update-browserslist-db": {
       "version": "1.0.11",
       "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz",
@@ -7380,13 +7675,13 @@
       }
     },
     "node_modules/vite": {
-      "version": "4.3.9",
-      "resolved": "https://registry.npmjs.org/vite/-/vite-4.3.9.tgz",
-      "integrity": "sha512-qsTNZjO9NoJNW7KnOrgYwczm0WctJ8m/yqYAMAK9Lxt4SoySUfS5S8ia9K7JHpa3KEeMfyF8LoJ3c5NeBJy6pg==",
+      "version": "4.4.2",
+      "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.2.tgz",
+      "integrity": "sha512-zUcsJN+UvdSyHhYa277UHhiJ3iq4hUBwHavOpsNUGsTgjBeoBlK8eDt+iT09pBq0h9/knhG/SPrZiM7cGmg7NA==",
       "dependencies": {
-        "esbuild": "^0.17.5",
-        "postcss": "^8.4.23",
-        "rollup": "^3.21.0"
+        "esbuild": "^0.18.10",
+        "postcss": "^8.4.24",
+        "rollup": "^3.25.2"
       },
       "bin": {
         "vite": "bin/vite.js"
@@ -7394,12 +7689,16 @@
       "engines": {
         "node": "^14.18.0 || >=16.0.0"
       },
+      "funding": {
+        "url": "https://github.com/vitejs/vite?sponsor=1"
+      },
       "optionalDependencies": {
         "fsevents": "~2.3.2"
       },
       "peerDependencies": {
         "@types/node": ">= 14",
         "less": "*",
+        "lightningcss": "^1.21.0",
         "sass": "*",
         "stylus": "*",
         "sugarss": "*",
@@ -7412,6 +7711,9 @@
         "less": {
           "optional": true
         },
+        "lightningcss": {
+          "optional": true
+        },
         "sass": {
           "optional": true
         },
@@ -7471,34 +7773,33 @@
       }
     },
     "node_modules/vitest": {
-      "version": "0.32.2",
-      "resolved": "https://registry.npmjs.org/vitest/-/vitest-0.32.2.tgz",
-      "integrity": "sha512-hU8GNNuQfwuQmqTLfiKcqEhZY72Zxb7nnN07koCUNmntNxbKQnVbeIS6sqUgR3eXSlbOpit8+/gr1KpqoMgWCQ==",
+      "version": "0.33.0",
+      "resolved": "https://registry.npmjs.org/vitest/-/vitest-0.33.0.tgz",
+      "integrity": "sha512-1CxaugJ50xskkQ0e969R/hW47za4YXDUfWJDxip1hwbnhUjYolpfUn2AMOulqG/Dtd9WYAtkHmM/m3yKVrEejQ==",
       "dependencies": {
         "@types/chai": "^4.3.5",
         "@types/chai-subset": "^1.3.3",
         "@types/node": "*",
-        "@vitest/expect": "0.32.2",
-        "@vitest/runner": "0.32.2",
-        "@vitest/snapshot": "0.32.2",
-        "@vitest/spy": "0.32.2",
-        "@vitest/utils": "0.32.2",
-        "acorn": "^8.8.2",
+        "@vitest/expect": "0.33.0",
+        "@vitest/runner": "0.33.0",
+        "@vitest/snapshot": "0.33.0",
+        "@vitest/spy": "0.33.0",
+        "@vitest/utils": "0.33.0",
+        "acorn": "^8.9.0",
         "acorn-walk": "^8.2.0",
         "cac": "^6.7.14",
         "chai": "^4.3.7",
-        "concordance": "^5.0.4",
         "debug": "^4.3.4",
         "local-pkg": "^0.4.3",
-        "magic-string": "^0.30.0",
-        "pathe": "^1.1.0",
+        "magic-string": "^0.30.1",
+        "pathe": "^1.1.1",
         "picocolors": "^1.0.0",
-        "std-env": "^3.3.2",
+        "std-env": "^3.3.3",
         "strip-literal": "^1.0.1",
         "tinybench": "^2.5.0",
-        "tinypool": "^0.5.0",
+        "tinypool": "^0.6.0",
         "vite": "^3.0.0 || ^4.0.0",
-        "vite-node": "0.32.2",
+        "vite-node": "0.33.0",
         "why-is-node-running": "^2.2.2"
       },
       "bin": {
@@ -7553,14 +7854,14 @@
       "integrity": "sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q=="
     },
     "node_modules/vitest/node_modules/vite-node": {
-      "version": "0.32.2",
-      "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-0.32.2.tgz",
-      "integrity": "sha512-dTQ1DCLwl2aEseov7cfQ+kDMNJpM1ebpyMMMwWzBvLbis8Nla/6c9WQcqpPssTwS6Rp/+U6KwlIj8Eapw4bLdA==",
+      "version": "0.33.0",
+      "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-0.33.0.tgz",
+      "integrity": "sha512-19FpHYbwWWxDr73ruNahC+vtEdza52kA90Qb3La98yZ0xULqV8A5JLNPUff0f5zID4984tW7l3DH2przTJUZSw==",
       "dependencies": {
         "cac": "^6.7.14",
         "debug": "^4.3.4",
-        "mlly": "^1.2.0",
-        "pathe": "^1.1.0",
+        "mlly": "^1.4.0",
+        "pathe": "^1.1.1",
         "picocolors": "^1.0.0",
         "vite": "^3.0.0 || ^4.0.0"
       },
@@ -7626,9 +7927,9 @@
       }
     },
     "node_modules/vue-eslint-parser/node_modules/semver": {
-      "version": "7.5.3",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz",
-      "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==",
+      "version": "7.5.4",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
+      "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
       "dependencies": {
         "lru-cache": "^6.0.0"
       },
@@ -7675,9 +7976,9 @@
       }
     },
     "node_modules/vue-router": {
-      "version": "4.2.2",
-      "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.2.2.tgz",
-      "integrity": "sha512-cChBPPmAflgBGmy3tBsjeoe3f3VOSG6naKyY5pjtrqLGbNEXdzCigFUHgBvp9e3ysAtFtEx7OLqcSDh/1Cq2TQ==",
+      "version": "4.2.4",
+      "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.2.4.tgz",
+      "integrity": "sha512-9PISkmaCO02OzPVOMq2w82ilty6+xJmQrarYZDkjZBfl4RvYAlt4PKnEX21oW4KTtWfa9OuO/b3qk1Od3AEdCQ==",
       "dependencies": {
         "@vue/devtools-api": "^6.5.0"
       },
@@ -7739,14 +8040,6 @@
         "node": ">=12"
       }
     },
-    "node_modules/well-known-symbols": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/well-known-symbols/-/well-known-symbols-2.0.0.tgz",
-      "integrity": "sha512-ZMjC3ho+KXo0BfJb7JgtQ5IBuvnShdlACNkKkdsqBmYw3bPAaJfPeYUo6tLUaT5tG/Gkh7xkpBhKRQ9e7pyg9Q==",
-      "engines": {
-        "node": ">=6"
-      }
-    },
     "node_modules/whatwg-encoding": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz",
@@ -7841,14 +8134,6 @@
         "node": ">=8"
       }
     },
-    "node_modules/word-wrap": {
-      "version": "1.2.3",
-      "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
-      "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
     "node_modules/wrap-ansi": {
       "version": "7.0.0",
       "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
diff --git a/package.json b/package.json
index d78fe416..8aaf8cc8 100644
--- a/package.json
+++ b/package.json
@@ -5,7 +5,7 @@
   "scripts": {
     "build": "vite build",
     "test": "TZ=Europe/Berlin vitest",
-    "lint": "prettier assets './**/*.{yml,yaml,toml}' --write && eslint assets/**/*.js assets/**/*.vue --fix",
+    "lint": "prettier assets './**/*.{yml,yaml}' --write && eslint assets/**/*.js assets/**/*.vue --fix",
     "dev": "vite",
     "histoire": "histoire dev",
     "playwright": "playwright test --ui"
@@ -29,20 +29,20 @@
     "eslint-config-prettier": "^8.5.0",
     "eslint-plugin-import": "^2.25.3",
     "eslint-plugin-node": "^11.1.0",
-    "eslint-plugin-prettier": "^4.0.0",
+    "eslint-plugin-prettier": "^5.0.0-alpha.1",
     "eslint-plugin-promise": "^6.0.0",
     "eslint-plugin-vue": "^9.3.0",
-    "happy-dom": "^9.5.0",
+    "happy-dom": "^10.0.3",
     "histoire": "^0.16.1",
     "json-to-pretty-yaml": "^1.2.2",
     "markty-toml": "^0.1.1",
-    "prettier": "^2.5.1",
+    "prettier": "^3.0.0",
     "prettier-plugin-toml": "^0.3.1",
     "rollup-plugin-visualizer": "^5.8.3",
     "smoothscroll-polyfill": "^0.4.4",
     "vite": "^4.2.1",
     "vite-plugin-toml": "^0.5.0",
-    "vitest": "^0.32.2",
+    "vitest": "^0.33.0",
     "vue": "^3.2.29",
     "vue-eslint-parser": "^9.1.0",
     "vue-hot-reload-api": "^2.3.4",
diff --git a/templates/definition/meter/kostal-piko-mp-plus.yaml b/templates/definition/meter/kostal-piko-mp-plus.yaml
index dce12357..41e9d78a 100644
--- a/templates/definition/meter/kostal-piko-mp-plus.yaml
+++ b/templates/definition/meter/kostal-piko-mp-plus.yaml
@@ -3,6 +3,9 @@ products:
   - brand: Kostal
     description:
       generic: Piko MP Plus
+  - brand: Steca
+    description:
+      generic: coolcept fleX
 linked:
   - template: kostal-ksem-inverter
     usage: grid
diff --git a/templates/definition/vehicle/offline.yaml b/templates/definition/vehicle/offline.yaml
index 31ae4e9a..9c7d9f17 100644
--- a/templates/definition/vehicle/offline.yaml
+++ b/templates/definition/vehicle/offline.yaml
@@ -11,15 +11,6 @@ params:
   - name: capacity
   - name: phases
     advanced: true
-  - name: mode
-    advanced: true
-  - name: minCurrent
-    advanced: true
-  - name: maxCurrent
-    advanced: true
-  - name: identifiers
-    advanced: true
-    type: stringlist
   - preset: vehicle-identify
 render: |
   type: custom
@@ -37,21 +28,4 @@ render: |
   soc:
     source: const
     value: 0
-  onIdentify:
-  {{- if .mode }}
-    mode: {{ .mode }}
-  {{- end }}
-    minSoc: 0 # fixed - no soc available
-    targetSoc: 100 # fixed - no soc available
-  {{- if .minCurrent }}
-    minCurrent: {{ .minCurrent }}
-  {{- end }}
-  {{- if .maxCurrent }}
-    maxCurrent: {{ .maxCurrent }}
-  {{- end }}
-  {{- if (len .identifiers) }}
-  identifiers:
-  {{-   range .identifiers }}
-  - {{ . }}
-  {{-   end }}
-  {{- end }}
+  {{ include "vehicle-identify" . }}
diff --git a/templates/definition/vehicle/seat-cupra.yaml b/templates/definition/vehicle/seat-cupra.yaml
index 4e747b76..3ebd497e 100644
--- a/templates/definition/vehicle/seat-cupra.yaml
+++ b/templates/definition/vehicle/seat-cupra.yaml
@@ -2,7 +2,7 @@ template: cupra
 products:
   - brand: Seat
     description:
-      generic: Cupra Born, My Cupra
+      generic: CupraConnect Gen4 (Born)
 params:
   - preset: vehicle-base
   - preset: vehicle-identify
diff --git a/templates/definition/vehicle/seat.yaml b/templates/definition/vehicle/seat.yaml
index 62b99403..c96cc2b0 100644
--- a/templates/definition/vehicle/seat.yaml
+++ b/templates/definition/vehicle/seat.yaml
@@ -2,8 +2,7 @@ template: seat
 products:
   - brand: Seat
     description:
-      de: Andere
-      en: Others
+      generic: CupraConnect Gen3 (Ateca, Leon, Formentor)
 params:
   - preset: vehicle-base
   - preset: vehicle-identify
diff --git a/templates/docs/meter/kostal-piko-mp-plus_1.yaml b/templates/docs/meter/kostal-piko-mp-plus_1.yaml
new file mode 100644
index 00000000..304a091b
--- /dev/null
+++ b/templates/docs/meter/kostal-piko-mp-plus_1.yaml
@@ -0,0 +1,10 @@
+product:
+  brand: Steca
+  description: coolcept fleX
+render:
+  - usage: pv
+    default: |
+      type: template
+      template: kostal-piko-mp-plus
+      usage: pv
+      host: 192.0.2.2 # IP-Adresse oder Hostname
diff --git a/templates/docs/vehicle/cupra_0.yaml b/templates/docs/vehicle/cupra_0.yaml
index b577cb02..c5997bf2 100644
--- a/templates/docs/vehicle/cupra_0.yaml
+++ b/templates/docs/vehicle/cupra_0.yaml
@@ -1,6 +1,6 @@
 product:
   brand: Seat
-  description: Cupra Born, My Cupra
+  description: CupraConnect Gen4 (Born)
 render:
   - default: |
       type: template
diff --git a/templates/docs/vehicle/offline_0.yaml b/templates/docs/vehicle/offline_0.yaml
index d97bed53..407e748f 100644
--- a/templates/docs/vehicle/offline_0.yaml
+++ b/templates/docs/vehicle/offline_0.yaml
@@ -15,10 +15,6 @@ render:
       capacity: 50 # Akkukapazität in kWh (Optional)
       phases: 3 # Die maximale Anzahl der Phasen welche genutzt werden können (Optional)
       mode: # Möglich sind Off, Now, MinPV und PV, oder leer wenn keiner definiert werden soll (Optional)
-      minCurrent: 6 # Definiert die minimale Stromstärke pro angeschlossener Phase mit welcher das Fahrzeug geladen werden soll (Optional)
-      maxCurrent: 16 # Definiert die maximale Stromstärke pro angeschlossener Phase mit welcher das Fahrzeug geladen werden soll (Optional)
-      identifiers: # Kann meist erst später eingetragen werden, siehe: https://docs.evcc.io/docs/guides/vehicles/#erkennung-des-fahrzeugs-an-der-wallbox (Optional)
-      mode: # Möglich sind Off, Now, MinPV und PV, oder leer wenn keiner definiert werden soll (Optional)
       minSoc: 25 # Ladung mit maximaler Geschwindigkeit bis zu dem angegeben Ladestand unabhängig PV-Erzeugung, wenn der Lademodus nicht auf 'Aus' steht (Optional)
       targetSoc: 80 # Bis zu welchem Ladestand (Soc) soll das Fahrzeug geladen werden (Optional)
       minCurrent: 6 # Definiert die minimale Stromstärke pro angeschlossener Phase mit welcher das Fahrzeug geladen werden soll (Optional)
diff --git a/templates/docs/vehicle/seat_0.yaml b/templates/docs/vehicle/seat_0.yaml
index d82ee71e..2fbbaade 100644
--- a/templates/docs/vehicle/seat_0.yaml
+++ b/templates/docs/vehicle/seat_0.yaml
@@ -1,6 +1,6 @@
 product:
   brand: Seat
-  description: Andere
+  description: CupraConnect Gen3 (Ateca, Leon, Formentor)
 render:
   - default: |
       type: template
diff --git a/templates/evcc.io/brands.json b/templates/evcc.io/brands.json
index 092397b8..54666919 100644
--- a/templates/evcc.io/brands.json
+++ b/templates/evcc.io/brands.json
@@ -164,6 +164,7 @@
   "Solarwatt",
   "Solax",
   "Sonnen",
+  "Steca",
   "Sungrow",
   "Sunsynk",
   "Tesla",
diff --git a/util/oauth/tokensource.go b/util/oauth/tokensource.go
index f996a0c7..2b776c26 100644
--- a/util/oauth/tokensource.go
+++ b/util/oauth/tokensource.go
@@ -3,7 +3,6 @@ package oauth
 import (
 	"errors"
 	"sync"
-	"time"
 
 	"github.com/imdario/mergo"
 	"golang.org/x/oauth2"
@@ -20,23 +19,33 @@ type TokenSource struct {
 }
 
 func RefreshTokenSource(token *oauth2.Token, refresher TokenRefresher) oauth2.TokenSource {
-	return &TokenSource{token: token, refresher: refresher}
+	ts := &TokenSource{
+		token:     token,
+		refresher: refresher,
+	}
+
+	return ts
 }
 
 func (ts *TokenSource) Token() (*oauth2.Token, error) {
 	ts.mu.Lock()
 	defer ts.mu.Unlock()
-	var err error
-	if ts.token == nil || time.Until(ts.token.Expiry) < time.Minute {
-		var token *oauth2.Token
-		if token, err = ts.refresher.RefreshToken(ts.token); err == nil {
-			if token.AccessToken == "" {
-				err = errors.New("token refresh failed to obtain access token")
-			} else {
-				err = ts.mergeToken(token)
-			}
-		}
+
+	if ts.token.Valid() {
+		return ts.token, nil
+	}
+
+	token, err := ts.refresher.RefreshToken(ts.token)
+	if err != nil {
+		return ts.token, err
+	}
+
+	if token.AccessToken == "" {
+		err = errors.New("token refresh failed to obtain access token")
+	} else {
+		err = ts.mergeToken(token)
 	}
+
 	return ts.token, err
 }
 
diff --git a/util/telemetry/charge.go b/util/telemetry/charge.go
index dde411d9..2822a2c5 100644
--- a/util/telemetry/charge.go
+++ b/util/telemetry/charge.go
@@ -92,7 +92,7 @@ func Persist(log *util.Logger) {
 // upload executes the actual upload.
 // Lock must be held when calling upload.
 func upload(log *util.Logger, chargePower, greenPower float64) error {
-	log.DEBUG.Printf("telemetry: charge: Δ%.0f/%.0fWh @ %.0fW", accGreenEnergy*1e3, accChargeEnergy*1e3, chargePower)
+	log.TRACE.Printf("telemetry: charge: Δ%.0f/%.0fWh @ %.0fW", accGreenEnergy*1e3, accChargeEnergy*1e3, chargePower)
 
 	data := InstanceChargeProgress{
 		InstanceID: instanceID,
diff --git a/vehicle/bluelink.go b/vehicle/bluelink.go
index e8a0276c..0b4850fe 100644
--- a/vehicle/bluelink.go
+++ b/vehicle/bluelink.go
@@ -8,6 +8,9 @@ import (
 	"github.com/evcc-io/evcc/vehicle/bluelink"
 )
 
+// https://github.com/Hacksore/bluelinky
+// https://github.com/Hyundai-Kia-Connect/hyundai_kia_connect_api/pull/353/files
+
 // Bluelink is an api.Vehicle implementation
 type Bluelink struct {
 	*embed
@@ -28,6 +31,7 @@ func NewHyundaiFromConfig(other map[string]interface{}) (api.Vehicle, error) {
 		CCSPApplicationID: bluelink.HyundaiAppID,
 		AuthClientID:      "64621b96-0f0d-11ec-82a8-0242ac130003",
 		BrandAuthUrl:      "https://eu-account.hyundai.com/auth/realms/euhyundaiidm/protocol/openid-connect/auth?client_id=%s&scope=openid%%20profile%%20email%%20phone&response_type=code&hkid_session_reset=true&redirect_uri=%s/api/v1/user/integration/redirect/login&ui_locales=%s&state=%s:%s",
+		PushType:          "GCM",
 	}
 
 	return newBluelinkFromConfig("hyundai", other, settings)
@@ -42,6 +46,7 @@ func NewKiaFromConfig(other map[string]interface{}) (api.Vehicle, error) {
 		CCSPApplicationID: bluelink.KiaAppID,
 		AuthClientID:      "572e0304-5f8d-4b4c-9dd5-41aa84eed160",
 		BrandAuthUrl:      "https://eu-account.kia.com/auth/realms/eukiaidm/protocol/openid-connect/auth?client_id=%s&scope=openid%%20profile%%20email%%20phone&response_type=code&hkid_session_reset=true&redirect_uri=%s/api/v1/user/integration/redirect/login&ui_locales=%s&state=%s:%s",
+		PushType:          "APNS",
 	}
 
 	return newBluelinkFromConfig("kia", other, settings)
@@ -81,7 +86,6 @@ func newBluelinkFromConfig(brand string, other map[string]interface{}, settings
 			return v.VIN
 		},
 	)
-
 	if err != nil {
 		return nil, err
 	}
diff --git a/vehicle/bluelink/api.go b/vehicle/bluelink/api.go
index a4ef029f..663c21e9 100644
--- a/vehicle/bluelink/api.go
+++ b/vehicle/bluelink/api.go
@@ -26,7 +26,6 @@ const (
 var ErrAuthFail = errors.New("authorization failed")
 
 // API implements the Kia/Hyundai bluelink api.
-// Based on https://github.com/Hacksore/bluelinky.
 type API struct {
 	*request.Helper
 	baseURI string
diff --git a/vehicle/bluelink/identity.go b/vehicle/bluelink/identity.go
index 934bb6d2..b3653bfe 100644
--- a/vehicle/bluelink/identity.go
+++ b/vehicle/bluelink/identity.go
@@ -36,6 +36,7 @@ type Config struct {
 	BasicToken        string
 	CCSPServiceID     string
 	CCSPApplicationID string
+	PushType          string
 }
 
 // Identity implements the Kia/Hyundai bluelink identity.
@@ -68,7 +69,7 @@ func (v *Identity) getDeviceID() (string, error) {
 	uuid := uuid.NewString()
 	data := map[string]interface{}{
 		"pushRegId": lo.RandomString(64, []rune("0123456789ABCDEF")),
-		"pushType":  "GCM",
+		"pushType":  v.config.PushType,
 		"uuid":      uuid,
 	}
 
diff --git a/vehicle/bluelink/stamps.go b/vehicle/bluelink/stamps.go
index b94757b6..2ea6b96e 100644
--- a/vehicle/bluelink/stamps.go
+++ b/vehicle/bluelink/stamps.go
@@ -11,7 +11,7 @@ import (
 )
 
 const (
-	KiaAppID     = "e7bcd186-a5fd-410d-92cb-6876a42288bd"
+	KiaAppID     = "a2b8469b-30a3-4361-8e13-6fceea8fbe74"
 	HyundaiAppID = "014d2225-8495-4735-812d-2616334fd15d"
 )
 
diff --git a/vehicle/porsche/identity.go b/vehicle/porsche/identity.go
index 3dd5555a..09aa7b98 100644
--- a/vehicle/porsche/identity.go
+++ b/vehicle/porsche/identity.go
@@ -165,7 +165,8 @@ func (v *Identity) Login(oc *oauth2.Config, user, password string) error {
 		return err
 	}
 
-	v.TokenSource = oc.TokenSource(cctx, token)
+	ts := oc.TokenSource(cctx, token)
+	v.TokenSource = oauth2.ReuseTokenSourceWithExpiry(token, ts, 15*time.Minute)
 
 	go v.refresh()
 
@@ -173,7 +174,7 @@ func (v *Identity) Login(oc *oauth2.Config, user, password string) error {
 }
 
 func (v *Identity) refresh() {
-	for range time.Tick(6 * time.Hour) {
+	for range time.Tick(5 * time.Minute) {
 		if _, err := v.Token(); err != nil {
 			v.log.ERROR.Printf("token refresh: %v", err)
 		}


Test patch:
diff --git a/assets/js/mixins/formatter.test.js b/assets/js/mixins/formatter.test.js
index 653b3496..b4b804ec 100644
--- a/assets/js/mixins/formatter.test.js
+++ b/assets/js/mixins/formatter.test.js
@@ -26,3 +26,23 @@ describe("fmtKWh", () => {
     expect(fmt.fmtKWh(56789.123, false, true, 2)).eq("56.789,12 Wh");
   });
 });
+
+describe("fmtPricePerKWh", () => {
+  test("should format with units", () => {
+    expect(fmt.fmtPricePerKWh(0.2, "EUR")).eq("20,0 ct/kWh");
+    expect(fmt.fmtPricePerKWh(0.2, "EUR", true)).eq("20,0 ct");
+    expect(fmt.fmtPricePerKWh(0.234, "USD")).eq("23,4 ct/kWh");
+    expect(fmt.fmtPricePerKWh(1234, "SEK")).eq("1.234,0 SEK/kWh");
+    expect(fmt.fmtPricePerKWh(0.2, "EUR", false, false)).eq("20,0");
+  });
+});
+
+describe("pricePerKWhUnit", () => {
+  test("should return correct unit", () => {
+    expect(fmt.pricePerKWhUnit("EUR")).eq("ct/kWh");
+    expect(fmt.pricePerKWhUnit("EUR", true)).eq("ct");
+    expect(fmt.pricePerKWhUnit("USD")).eq("ct/kWh");
+    expect(fmt.pricePerKWhUnit("SEK")).eq("SEK/kWh");
+    expect(fmt.pricePerKWhUnit("SEK", true)).eq("SEK");
+  });
+});
diff --git a/core/prioritizer/prioritizer_test.go b/core/prioritizer/prioritizer_test.go
index f3d322c6..cb533718 100644
--- a/core/prioritizer/prioritizer_test.go
+++ b/core/prioritizer/prioritizer_test.go
@@ -11,13 +11,15 @@ import (
 func TestPrioritzer(t *testing.T) {
 	ctrl := gomock.NewController(t)
 
-	p := New()
+	p := New(nil)
 
 	lo := loadpoint.NewMockAPI(ctrl)
-	lo.EXPECT().Priority().Return(0).AnyTimes()
+	lo.EXPECT().Title().AnyTimes()
+	lo.EXPECT().GetPriority().Return(0).AnyTimes()
 
 	hi := loadpoint.NewMockAPI(ctrl)
-	hi.EXPECT().Priority().Return(1).AnyTimes()
+	hi.EXPECT().Title().AnyTimes()
+	hi.EXPECT().GetPriority().Return(1).AnyTimes()
 
 	// no additional power available
 	lo.EXPECT().GetChargePowerFlexibility().Return(300.0)
diff --git a/tests/sessions.spec.js b/tests/sessions.spec.js
index a3d7c558..dc0a0dc8 100644
--- a/tests/sessions.spec.js
+++ b/tests/sessions.spec.js
@@ -1,6 +1,9 @@
-const { test, expect } = require("@playwright/test");
+const { test, expect, devices } = require("@playwright/test");
 const { start, stop } = require("./evcc");
 
+const mobile = devices["iPhone 12 Mini"].viewport;
+const desktop = devices["Desktop Chrome"].viewport;
+
 test.beforeAll(async () => {
   await start("basics.evcc.yaml", "sessions.sql");
 });
@@ -30,14 +33,65 @@ test.describe("basics", async () => {
     await expect(page.getByTestId("sessions-nodata")).toHaveCount(0);
     await expect(page.getByRole("table")).toBeVisible();
     await expect(page.getByTestId("sessions-head")).toHaveCount(1);
-    await expect(page.getByText("Charged 20.0 kWh")).toBeVisible();
-    await expect(page.getByText("Solar 67.3%")).toBeVisible();
-    await expect(page.getByText("Σ Price 5.50 €")).toBeVisible();
-    await expect(page.getByText("Ø Price 27.5 ct/kWh")).toBeVisible();
+
+    await expect(page.getByTestId("sessions-head-energy")).toContainText("ChargedkWh");
+    await expect(page.getByTestId("sessions-foot-energy")).toBeVisible("20.0");
+
+    await expect(page.getByTestId("sessions-head-solar")).toContainText("Solar%");
+    await expect(page.getByTestId("sessions-foot-solar")).toBeVisible("67.3");
+
+    await expect(page.getByTestId("sessions-head-price")).toContainText("Σ Price€");
+    await expect(page.getByTestId("sessions-foot-price")).toBeVisible("5.50");
+
+    await expect(page.getByTestId("sessions-head-avgPrice")).toContainText("⌀ Pricect/kWh");
+    await expect(page.getByTestId("sessions-foot-avgPrice")).toBeVisible("27.5");
+
     await expect(page.getByTestId("sessions-entry")).toHaveCount(4);
   });
 });
 
+test.describe("mobile basics", async () => {
+  test("column select", async ({ page }) => {
+    await page.setViewportSize(mobile);
+    await page.goto("/#/sessions?year=2023&month=5");
+
+    // hidden columns
+    await expect(page.getByTestId("sessions-head-energy")).not.toBeVisible();
+    await expect(page.getByTestId("sessions-foot-energy")).not.toBeVisible();
+    await expect(page.getByTestId("sessions-head-solar")).not.toBeVisible();
+    await expect(page.getByTestId("sessions-foot-solar")).not.toBeVisible();
+    await expect(page.getByTestId("sessions-head-price")).not.toBeVisible();
+    await expect(page.getByTestId("sessions-foot-price")).not.toBeVisible();
+    await expect(page.getByTestId("sessions-head-avgPrice")).not.toBeVisible();
+    await expect(page.getByTestId("sessions-foot-avgPrice")).not.toBeVisible();
+
+    await expect(page.getByTestId("sessions-head-mobile")).toContainText("ChargedkWh");
+    await expect(page.getByTestId("sessions-foot-mobile")).toBeVisible("20.0");
+
+    await page.getByTestId("mobile-column").selectOption("Solar");
+    await expect(page.getByTestId("sessions-head-mobile")).toContainText("Solar%");
+    await expect(page.getByTestId("sessions-foot-mobile")).toBeVisible("67.3");
+
+    await page.getByTestId("mobile-column").selectOption("Σ Price");
+    await expect(page.getByTestId("sessions-head-mobile")).toContainText("Σ Price€");
+    await expect(page.getByTestId("sessions-foot-mobile")).toBeVisible("5.50");
+
+    await page.getByTestId("mobile-column").selectOption("⌀ Price");
+    await expect(page.getByTestId("sessions-head-mobile")).toContainText("⌀ Pricect/kWh");
+    await expect(page.getByTestId("sessions-foot-mobile")).toBeVisible("27.5");
+  });
+
+  test("keep selection when paging", async ({ page }) => {
+    await page.setViewportSize(mobile);
+    await page.goto("/#/sessions?year=2023&month=5");
+
+    await page.getByTestId("mobile-column").selectOption("Solar");
+    await page.getByRole("link", { name: "Apr" }).click();
+    await page.getByRole("link", { name: "May" }).click();
+    await expect(page.getByTestId("sessions-head-mobile")).toContainText("Solar%");
+  });
+});
+
 test.describe("paging", async () => {
   test("prev/next links", async ({ page }) => {
     await page.goto("/#/sessions?year=2023&month=5");
@@ -58,81 +112,121 @@ test.describe("paging", async () => {
   });
 });
 
-test.describe("filter", async () => {
-  test("by vehicle", async ({ page }) => {
-    await page.goto("/#/sessions?year=2023&month=5");
-    await expect(page.getByTestId("sessions-entry")).toHaveCount(4);
+for (const [name, viewport] of Object.entries({ desktop, mobile })) {
+  test.describe(`filter on ${name}`, async () => {
+    test("by vehicle", async ({ page }) => {
+      await page.setViewportSize(viewport);
+      await page.goto("/#/sessions?year=2023&month=5");
+      await expect(page.getByTestId("sessions-entry")).toHaveCount(4);
 
-    await page.getByLabel("all vehicles").selectOption("blauer e-Golf (2)");
-    await expect(page.getByTestId("sessions-entry")).toHaveCount(2);
-    await expect(page.getByTestId("sessions-entry").nth(0)).toHaveText(/blauer e-Golf/);
-    await expect(page.getByTestId("sessions-entry").nth(1)).toHaveText(/blauer e-Golf/);
+      await page
+        .getByTestId("filter-vehicle")
+        .locator("visible=true")
+        .selectOption("blauer e-Golf (2)");
+      await expect(page.getByTestId("sessions-entry")).toHaveCount(2);
+      await expect(page.getByTestId("sessions-entry").nth(0)).toHaveText(/blauer e-Golf/);
+      await expect(page.getByTestId("sessions-entry").nth(1)).toHaveText(/blauer e-Golf/);
 
-    await page.getByLabel("all vehicles").selectOption("weißes Model 3 (2)");
-    await expect(page.getByTestId("sessions-entry")).toHaveCount(2);
-    await expect(page.getByTestId("sessions-entry").nth(0)).toHaveText(/weißes Model 3/);
-    await expect(page.getByTestId("sessions-entry").nth(1)).toHaveText(/weißes Model 3/);
+      await page
+        .getByTestId("filter-vehicle")
+        .locator("visible=true")
+        .selectOption("weißes Model 3 (2)");
+      await expect(page.getByTestId("sessions-entry")).toHaveCount(2);
+      await expect(page.getByTestId("sessions-entry").nth(0)).toHaveText(/weißes Model 3/);
+      await expect(page.getByTestId("sessions-entry").nth(1)).toHaveText(/weißes Model 3/);
 
-    await page.getByLabel("all vehicles").selectOption("all vehicles (4)");
-    await expect(page.getByTestId("sessions-entry")).toHaveCount(4);
-  });
+      await page
+        .getByTestId("filter-vehicle")
+        .locator("visible=true")
+        .selectOption("all vehicles (4)");
+      await expect(page.getByTestId("sessions-entry")).toHaveCount(4);
+    });
 
-  test("by loadpoint", async ({ page }) => {
-    await page.goto("/#/sessions?year=2023&month=5");
-    await expect(page.getByTestId("sessions-entry")).toHaveCount(4);
+    test("by loadpoint", async ({ page }) => {
+      await page.setViewportSize(viewport);
+      await page.goto("/#/sessions?year=2023&month=5");
+      await expect(page.getByTestId("sessions-entry")).toHaveCount(4);
 
-    await page.getByLabel("all charging points").selectOption("Carport (3)");
-    await expect(page.getByTestId("sessions-entry")).toHaveCount(3);
-    await expect(page.getByTestId("sessions-entry").nth(0)).toHaveText(/Carport/);
-    await expect(page.getByTestId("sessions-entry").nth(1)).toHaveText(/Carport/);
-    await expect(page.getByTestId("sessions-entry").nth(2)).toHaveText(/Carport/);
+      await page
+        .getByTestId("filter-loadpoint")
+        .locator("visible=true")
+        .selectOption("Carport (3)");
+      await expect(page.getByTestId("sessions-entry")).toHaveCount(3);
+      await expect(page.getByTestId("sessions-entry").nth(0)).toHaveText(/Carport/);
+      await expect(page.getByTestId("sessions-entry").nth(1)).toHaveText(/Carport/);
+      await expect(page.getByTestId("sessions-entry").nth(2)).toHaveText(/Carport/);
 
-    await page.getByLabel("all charging points").selectOption("Garage (1)");
-    await expect(page.getByTestId("sessions-entry")).toHaveCount(1);
-    await expect(page.getByTestId("sessions-entry").nth(0)).toHaveText(/Garage/);
+      await page.getByTestId("filter-loadpoint").locator("visible=true").selectOption("Garage (1)");
+      await expect(page.getByTestId("sessions-entry")).toHaveCount(1);
+      await expect(page.getByTestId("sessions-entry").nth(0)).toHaveText(/Garage/);
 
-    await page.getByLabel("all charging points").selectOption("all charging points (4)");
-    await expect(page.getByTestId("sessions-entry")).toHaveCount(4);
-  });
+      await page
+        .getByTestId("filter-loadpoint")
+        .locator("visible=true")
+        .selectOption("all charging points (4)");
+      await expect(page.getByTestId("sessions-entry")).toHaveCount(4);
+    });
 
-  test("by vehicle and loadpoint", async ({ page }) => {
-    await page.goto("/#/sessions?year=2023&month=5");
+    test("by vehicle and loadpoint", async ({ page }) => {
+      await page.setViewportSize(viewport);
+      await page.goto("/#/sessions?year=2023&month=5");
 
-    await page.getByLabel("all charging points").selectOption("Carport (3)");
-    await page.getByLabel("all vehicles").selectOption("weißes Model 3 (1)");
-    await expect(page.getByTestId("sessions-entry")).toHaveCount(1);
-    await expect(page.getByTestId("sessions-entry")).toHaveText(/Carport/);
-    await expect(page.getByTestId("sessions-entry")).toHaveText(/weißes Model 3/);
+      await page
+        .getByTestId("filter-loadpoint")
+        .locator("visible=true")
+        .selectOption("Carport (3)");
+      await page
+        .getByTestId("filter-vehicle")
+        .locator("visible=true")
+        .selectOption("weißes Model 3 (1)");
+      await expect(page.getByTestId("sessions-entry")).toHaveCount(1);
+      await expect(page.getByTestId("sessions-entry")).toHaveText(/Carport/);
+      await expect(page.getByTestId("sessions-entry")).toHaveText(/weißes Model 3/);
 
-    await page.getByLabel("all vehicles").selectOption("blauer e-Golf (2)");
-    await expect(page.getByTestId("sessions-entry")).toHaveCount(2);
-    await expect(page.getByTestId("sessions-entry").nth(0)).toHaveText(/Carport/);
-    await expect(page.getByTestId("sessions-entry").nth(0)).toHaveText(/blauer e-Golf/);
-    await expect(page.getByTestId("sessions-entry").nth(1)).toHaveText(/Carport/);
-    await expect(page.getByTestId("sessions-entry").nth(1)).toHaveText(/blauer e-Golf/);
-  });
+      await page
+        .getByTestId("filter-vehicle")
+        .locator("visible=true")
+        .selectOption("blauer e-Golf (2)");
+      await expect(page.getByTestId("sessions-entry")).toHaveCount(2);
+      await expect(page.getByTestId("sessions-entry").nth(0)).toHaveText(/Carport/);
+      await expect(page.getByTestId("sessions-entry").nth(0)).toHaveText(/blauer e-Golf/);
+      await expect(page.getByTestId("sessions-entry").nth(1)).toHaveText(/Carport/);
+      await expect(page.getByTestId("sessions-entry").nth(1)).toHaveText(/blauer e-Golf/);
+    });
 
-  test("by vehicle and loadpoint disabled options", async ({ page }) => {
-    await page.goto("/#/sessions?year=2023&month=5");
+    test("by vehicle and loadpoint disabled options", async ({ page }) => {
+      await page.setViewportSize(viewport);
+      await page.goto("/#/sessions?year=2023&month=5");
 
-    await page.getByLabel("all vehicles").selectOption("blauer e-Golf (2)");
-    const option = page.locator("option[value=Garage]");
-    await expect(option).toHaveAttribute("disabled", "");
-    await expect(option).toHaveText("Garage (0)");
-  });
+      await page
+        .getByTestId("filter-vehicle")
+        .locator("visible=true")
+        .selectOption("blauer e-Golf (2)");
+      const option = page
+        .getByTestId("filter-loadpoint")
+        .locator("visible=true")
+        .locator("option[value=Garage]");
+      await expect(option).toHaveAttribute("disabled", "");
+      await expect(option).toHaveText("Garage (0)");
+    });
 
-  test("keep filter when paging", async ({ page }) => {
-    await page.goto("/#/sessions?year=2023&month=5");
+    test("keep filter when paging", async ({ page }) => {
+      await page.setViewportSize(viewport);
+      await page.goto("/#/sessions?year=2023&month=5");
 
-    await page.getByLabel("all charging points").selectOption("Carport (3)");
-    await expect(page.getByTestId("sessions-entry")).toHaveCount(3);
-    await page.getByRole("link", { name: "June" }).click();
-    await page.getByRole("link", { name: "May" }).click();
-    await expect(page.getByTestId("sessions-entry")).toHaveCount(3);
+      await page
+        .getByTestId("filter-loadpoint")
+        .locator("visible=true")
+        .selectOption("Carport (3)");
+      await expect(page.getByTestId("sessions-entry")).toHaveCount(3);
+      await page.getByRole("link", { name: "Jun" }).click();
+      await page.getByRole("link", { name: "May" }).click();
+      await expect(page.getByTestId("sessions-entry")).toHaveCount(3);
+    });
   });
-});
+}
 
-test.describe("columns", async () => {
+test.describe("columns desktop", async () => {
   test("show vehicle column if multiple different exist", async ({ page }) => {
     await page.goto("/#/sessions?year=2023&month=5");
     await expect(page.getByTestId("vehicle")).toBeVisible();
@@ -151,11 +245,11 @@ test.describe("columns", async () => {
   });
   test("show co2 column it has values", async ({ page }) => {
     await page.goto("/#/sessions?year=2023&month=3");
-    await expect(page.getByTestId("co2")).toBeVisible();
+    await expect(page.getByTestId("sessions-head-co2")).toBeVisible();
   });
   test("hide co2 column if it doesnt have values", async ({ page }) => {
     await page.goto("/#/sessions?year=2023&month=5");
-    await expect(page.getByTestId("co2")).toHaveCount(0);
+    await expect(page.getByTestId("sessions-head-co2")).toHaveCount(0);
   });
 });