diff --git a/.oxfmtrc.json b/.oxfmtrc.json index a4d38b60b..0bc993fcb 100644 --- a/.oxfmtrc.json +++ b/.oxfmtrc.json @@ -7,6 +7,7 @@ "endOfLine": "lf", "trailingComma": "all", "ignorePatterns": [ + "pnpm-lock.yaml", "node_modules", ".turbo", "dist", diff --git a/backend/__tests__/__integration__/global-setup.ts b/backend/__tests__/__integration__/global-setup.ts index 2aac2acd5..659b9a6fe 100644 --- a/backend/__tests__/__integration__/global-setup.ts +++ b/backend/__tests__/__integration__/global-setup.ts @@ -31,7 +31,14 @@ export async function setup(): Promise { process.env["REDIS_URI"] = redisUrl; } -export async function teardown(): Promise { +async function stopContainers(): Promise { await startedMongoContainer?.stop(); await startedRedisContainer?.stop(); } + +export async function teardown(): Promise { + await stopContainers(); +} + +process.on("SIGTERM", stopContainers); +process.on("SIGINT", stopContainers); diff --git a/backend/package.json b/backend/package.json index c6f6bf79d..ba28bc4ce 100644 --- a/backend/package.json +++ b/backend/package.json @@ -77,17 +77,17 @@ "@types/swagger-stats": "0.95.11", "@types/ua-parser-js": "0.7.36", "@types/uuid": "10.0.0", - "@vitest/coverage-v8": "4.0.8", + "@vitest/coverage-v8": "4.0.15", "concurrently": "8.2.2", "openapi3-ts": "2.0.2", - "oxlint": "1.33.0", - "oxlint-tsgolint": "0.9.0", + "oxlint": "1.34.0", + "oxlint-tsgolint": "0.9.2", "readline-sync": "1.4.10", "supertest": "7.1.4", - "testcontainers": "11.4.0", + "testcontainers": "11.10.0", "tsx": "4.16.2", "typescript": "5.9.3", - "vitest": "4.0.8" + "vitest": "4.0.15" }, "engines": { "node": "24.11.0 || 22.21.0" diff --git a/frontend/package.json b/frontend/package.json index b103b233a..57e1d5ab6 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -82,7 +82,7 @@ "@types/seedrandom": "3.0.2", "@types/subset-font": "1.4.3", "@types/throttle-debounce": "5.0.2", - "@vitest/coverage-v8": "4.0.8", + "@vitest/coverage-v8": "4.0.15", "autoprefixer": "10.4.20", "concurrently": "8.2.2", "eslint": "9.39.1", @@ -93,8 +93,8 @@ "madge": "8.0.0", "magic-string": "0.30.17", "normalize.css": "8.0.1", - "oxlint": "1.33.0", - "oxlint-tsgolint": "0.9.0", + "oxlint": "1.34.0", + "oxlint-tsgolint": "0.9.2", "postcss": "8.4.31", "sass": "1.70.0", "subset-font": "2.3.0", @@ -103,13 +103,12 @@ "unplugin-inject-preload": "3.0.0", "vite": "7.1.12", "vite-bundle-visualizer": "1.2.1", - "vite-plugin-checker": "0.11.0", "vite-plugin-filter-replace": "0.1.14", "vite-plugin-html-inject": "1.1.2", "vite-plugin-inspect": "11.3.3", "vite-plugin-minify": "2.1.0", "vite-plugin-pwa": "1.1.0", - "vitest": "4.0.8" + "vitest": "4.0.15" }, "browserslist": [ "defaults", diff --git a/frontend/src/html/pages/settings.html b/frontend/src/html/pages/settings.html index 17268b7e2..6b85259c2 100644 --- a/frontend/src/html/pages/settings.html +++ b/frontend/src/html/pages/settings.html @@ -196,7 +196,7 @@
diff --git a/frontend/src/styles/core.scss b/frontend/src/styles/core.scss index de893592d..675cfcde2 100644 --- a/frontend/src/styles/core.scss +++ b/frontend/src/styles/core.scss @@ -403,6 +403,11 @@ key { } } + .userIcon { + display: grid; + border-radius: 0; + } + .loading { font-size: 0.8em; line-height: 0.8em; diff --git a/frontend/src/styles/popups.scss b/frontend/src/styles/popups.scss index 5dd73f23b..ae2d8f8d4 100644 --- a/frontend/src/styles/popups.scss +++ b/frontend/src/styles/popups.scss @@ -2209,8 +2209,7 @@ body.darkMode { } } .notificationHistory .list .item { - grid-template-areas: "indicator title" "indicator body"; - grid-template-columns: 0.25rem calc(100% - 0.25rem); + grid-template-areas: "indicator title buttons" "indicator body buttons"; .title { font-size: 0.75rem; color: var(--sub-color); @@ -2218,6 +2217,9 @@ body.darkMode { .body { opacity: 1; } + .highlight { + color: var(--main-color) !important; + } } .accountAlerts { .title { diff --git a/frontend/src/styles/settings.scss b/frontend/src/styles/settings.scss index f37ed4b68..2dcc02cab 100644 --- a/frontend/src/styles/settings.scss +++ b/frontend/src/styles/settings.scss @@ -50,6 +50,7 @@ .settingsGroup { display: grid; gap: 2rem; + overflow: hidden; &.quickNav { justify-content: center; .links { diff --git a/frontend/src/ts/auth.ts b/frontend/src/ts/auth.ts index 8e33f5822..da87b66a2 100644 --- a/frontend/src/ts/auth.ts +++ b/frontend/src/ts/auth.ts @@ -45,14 +45,11 @@ async function sendVerificationEmail(): Promise { Loader.show(); qs(".sendVerificationEmail")?.disable(); - const result = await Ape.users.verificationEmail(); + const response = await Ape.users.verificationEmail(); qs(".sendVerificationEmail")?.enable(); - if (result.status !== 200) { + if (response.status !== 200) { Loader.hide(); - Notifications.add( - "Failed to request verification email: " + result.body.message, - -1, - ); + Notifications.add("Failed to request verification email", -1, { response }); } else { Loader.hide(); Notifications.add("Verification email sent", 1); diff --git a/frontend/src/ts/constants/languages.ts b/frontend/src/ts/constants/languages.ts index af5eddfd3..fd963dfed 100644 --- a/frontend/src/ts/constants/languages.ts +++ b/frontend/src/ts/constants/languages.ts @@ -188,7 +188,12 @@ export const LanguageGroups: Record = { swahili: ["swahili_1k"], maori: ["maori_1k"], catalan: ["catalan", "catalan_1k"], - bulgarian: ["bulgarian", "bulgarian_latin"], + bulgarian: [ + "bulgarian", + "bulgarian_1k", + "bulgarian_latin", + "bulgarian_latin_1k", + ], bosnian: ["bosnian", "bosnian_4k"], esperanto: [ "esperanto", diff --git a/frontend/src/ts/db.ts b/frontend/src/ts/db.ts index a8e430152..0089fc074 100644 --- a/frontend/src/ts/db.ts +++ b/frontend/src/ts/db.ts @@ -299,7 +299,7 @@ export async function getUserResults(offset?: number): Promise { const response = await Ape.results.get({ query: { offset } }); if (response.status !== 200) { - Notifications.add("Error getting results: " + response.body.message, -1); + Notifications.add("Error getting results", -1, { response }); return false; } @@ -357,10 +357,7 @@ export async function addCustomTheme( const response = await Ape.users.addCustomTheme({ body: { ...theme } }); if (response.status !== 200) { - Notifications.add( - "Error adding custom theme: " + response.body.message, - -1, - ); + Notifications.add("Error adding custom theme", -1, { response }); return false; } @@ -400,10 +397,7 @@ export async function editCustomTheme( body: { themeId, theme: newTheme }, }); if (response.status !== 200) { - Notifications.add( - "Error editing custom theme: " + response.body.message, - -1, - ); + Notifications.add("Error editing custom theme", -1, { response }); return false; } @@ -427,10 +421,7 @@ export async function deleteCustomTheme(themeId: string): Promise { const response = await Ape.users.deleteCustomTheme({ body: { themeId } }); if (response.status !== 200) { - Notifications.add( - "Error deleting custom theme: " + response.body.message, - -1, - ); + Notifications.add("Error deleting custom theme", -1, { response }); return false; } @@ -923,7 +914,7 @@ export async function saveConfig(config: Partial): Promise { if (isAuthenticated()) { const response = await Ape.configs.save({ body: config }); if (response.status !== 200) { - Notifications.add("Failed to save config: " + response.body.message, -1); + Notifications.add("Failed to save config", -1, { response }); } } } @@ -932,7 +923,7 @@ export async function resetConfig(): Promise { if (isAuthenticated()) { const response = await Ape.configs.delete(); if (response.status !== 200) { - Notifications.add("Failed to reset config: " + response.body.message, -1); + Notifications.add("Failed to reset config", -1, { response }); } } } @@ -1055,10 +1046,7 @@ export async function getTestActivityCalendar( Loader.show(); const response = await Ape.users.getTestActivity(); if (response.status !== 200) { - Notifications.add( - "Error getting test activities: " + response.body.message, - -1, - ); + Notifications.add("Error getting test activities", -1, { response }); Loader.hide(); return undefined; } diff --git a/frontend/src/ts/elements/account-settings/ape-key-table.ts b/frontend/src/ts/elements/account-settings/ape-key-table.ts index 83c27771c..0d845b7c0 100644 --- a/frontend/src/ts/elements/account-settings/ape-key-table.ts +++ b/frontend/src/ts/elements/account-settings/ape-key-table.ts @@ -27,7 +27,8 @@ const editApeKey = new SimpleModal({ if (response.status !== 200) { return { status: -1, - message: "Failed to update key: " + response.body.message, + message: "Failed to update key", + notificationOptions: { response }, }; } return { @@ -53,7 +54,8 @@ const deleteApeKeyModal = new SimpleModal({ if (response.status !== 200) { return { status: -1, - message: "Failed to delete key: " + response.body.message, + message: "Failed to delete key", + notificationOptions: { response }, }; } @@ -128,7 +130,8 @@ const generateApeKey = new SimpleModal({ if (response.status !== 200) { return { status: -1, - message: "Failed to generate key: " + response.body.message, + message: "Failed to generate key", + notificationOptions: { response }, }; } @@ -174,7 +177,7 @@ async function getData(): Promise { void update(); return false; } - Notifications.add("Error getting ape keys: " + response.body.message, -1); + Notifications.add("Error getting ape keys", -1, { response }); return false; } @@ -261,7 +264,7 @@ async function toggleActiveKey(keyId: string): Promise { }); Loader.hide(); if (response.status !== 200) { - Notifications.add("Failed to update key: " + response.body.message, -1); + Notifications.add("Failed to update key", -1, { response }); return; } key.enabled = !key.enabled; diff --git a/frontend/src/ts/elements/account-settings/blocked-user-table.ts b/frontend/src/ts/elements/account-settings/blocked-user-table.ts index 57839e54a..9bb28e169 100644 --- a/frontend/src/ts/elements/account-settings/blocked-user-table.ts +++ b/frontend/src/ts/elements/account-settings/blocked-user-table.ts @@ -24,10 +24,7 @@ async function getData(): Promise { if (response.status !== 200) { blockedUsers = []; - Notifications.add( - "Error getting blocked users: " + response.body.message, - -1, - ); + Notifications.add("Error getting blocked users", -1, { response }); return false; } diff --git a/frontend/src/ts/elements/alerts.ts b/frontend/src/ts/elements/alerts.ts index 295c292fd..e94aa4612 100644 --- a/frontend/src/ts/elements/alerts.ts +++ b/frontend/src/ts/elements/alerts.ts @@ -6,7 +6,12 @@ import * as NotificationEvent from "../observables/notification-event"; import * as BadgeController from "../controllers/badge-controller"; import * as Notifications from "../elements/notifications"; import * as ConnectionState from "../states/connection"; -import { escapeHTML } from "../utils/misc"; +import { + applyReducedMotion, + createErrorMessage, + escapeHTML, + promiseAnimate, +} from "../utils/misc"; import AnimatedModal from "../utils/animated-modal"; import { updateXp as accountPageUpdateProfile } from "./profile"; import { MonkeyMail } from "@monkeytype/schemas/users"; @@ -29,10 +34,17 @@ let mailToMarkRead: string[] = []; let mailToDelete: string[] = []; type State = { - notifications: { message: string; level: number; customTitle?: string }[]; + notifications: { + id: string; + title: string; + message: string; + level: number; + details?: string | object; + }[]; psas: { message: string; level: number }[]; }; +let notificationId = 0; const state: State = { notifications: [], psas: [], @@ -289,28 +301,29 @@ function fillNotifications(): void { } else { notificationHistoryListEl.empty(); for (const n of state.notifications) { - const { message, level, customTitle } = n; - let title = "Notice"; + const { message, level, title } = n; + let levelClass = "sub"; if (level === -1) { levelClass = "error"; - title = "Error"; } else if (level === 1) { levelClass = "main"; - title = "Success"; - } - - if (customTitle !== undefined) { - title = customTitle; } notificationHistoryListEl.prependHtml(` -
+
${title}
${escapeHTML(message)}
+
+ ${ + n.details !== undefined + ? `` + : `` + } +
`); } @@ -396,15 +409,89 @@ function updateClaimDeleteAllButton(): void { } } +async function copyNotificationToClipboard(target: HTMLElement): Promise { + const id = (target as HTMLElement | null) + ?.closest(".item") + ?.getAttribute("data-id") + ?.toString(); + + if (id === undefined) { + throw new Error("Notification ID is undefined"); + } + const notification = state.notifications.find((it) => it.id === id); + if (notification === undefined) return; + + const icon = target.querySelector("i") as HTMLElement; + + try { + await navigator.clipboard.writeText( + JSON.stringify( + { + title: notification.title, + message: notification.message, + details: notification.details, + }, + null, + 4, + ), + ); + + const duration = applyReducedMotion(100); + + await promiseAnimate(icon, { + scale: [1, 0.8], + opacity: [1, 0], + duration, + }); + icon.classList.remove("fa-clipboard"); + icon.classList.add("fa-check", "highlight"); + await promiseAnimate(icon, { + scale: [0.8, 1], + opacity: [0, 1], + duration, + }); + + await promiseAnimate(icon, { + scale: [1, 0.8], + opacity: [1, 0], + delay: 3000, + duration, + }); + icon.classList.remove("fa-check", "highlight"); + icon.classList.add("fa-clipboard"); + + await promiseAnimate(icon, { + scale: [0.8, 1], + opacity: [0, 1], + duration, + }); + } catch (e: unknown) { + const msg = createErrorMessage(e, "Could not copy to clipboard"); + Notifications.add(msg, -1); + } +} + qs("header nav .showAlerts")?.on("click", () => { void show(); }); -NotificationEvent.subscribe((message, level, customTitle) => { +NotificationEvent.subscribe((message, level, options) => { + let title = "Notice"; + if (level === -1) { + title = "Error"; + } else if (level === 1) { + title = "Success"; + } + if (options.customTitle !== undefined) { + title = options.customTitle; + } + state.notifications.push({ + id: (notificationId++).toString(), + title, message, level, - customTitle, + details: options.details, }); if (state.notifications.length > 25) { state.notifications.shift(); @@ -495,5 +582,11 @@ const modal = new AnimatedModal({ markReadAlert(id); }); + + alertsPopupEl + .qs(".notificationHistory .list") + ?.onChild("click", ".item .buttons .copyNotification", (e) => { + void copyNotificationToClipboard(e.target as HTMLElement); + }); }, }); diff --git a/frontend/src/ts/elements/input-validation.ts b/frontend/src/ts/elements/input-validation.ts index 21316ae46..42bb455a2 100644 --- a/frontend/src/ts/elements/input-validation.ts +++ b/frontend/src/ts/elements/input-validation.ts @@ -8,7 +8,7 @@ import { } from "@monkeytype/schemas/configs"; import Config, { setConfig } from "../config"; import * as Notifications from "../elements/notifications"; -import { ElementWithUtils } from "../utils/dom"; +import { DomUtilsEvent, ElementWithUtils } from "../utils/dom"; export type ValidationResult = { status: "checking" | "success" | "failed" | "warning"; @@ -60,7 +60,7 @@ export function createInputEventHandler( callback: (result: ValidationResult) => void, validation: Validation, inputValueConvert?: (val: string) => T, -): (e: Event) => Promise { +): (e: DomUtilsEvent) => Promise { let callIsValid = validation.isValid !== undefined ? debounceIfNeeded( diff --git a/frontend/src/ts/elements/notifications.ts b/frontend/src/ts/elements/notifications.ts index c15e0638f..e85593a5d 100644 --- a/frontend/src/ts/elements/notifications.ts +++ b/frontend/src/ts/elements/notifications.ts @@ -5,6 +5,7 @@ import * as NotificationEvent from "../observables/notification-event"; import { convertRemToPixels } from "../utils/numbers"; import { animate } from "animejs"; import { qsr } from "../utils/dom"; +import { CommonResponsesType } from "@monkeytype/contracts/util/api"; const notificationCenter = qsr("#notificationCenter"); const notificationCenterHistory = notificationCenter.qsr(".history"); @@ -107,6 +108,7 @@ class Notification { visibleStickyNotifications++; updateClearAllButton(); } + notificationCenterHistory.prependHtml(`
${icon}
${title}
${this.message}
@@ -270,6 +272,8 @@ export type AddNotificationOptions = { customIcon?: string; closeCallback?: () => void; allowHTML?: boolean; + details?: object | string; + response?: CommonResponsesType; }; export function add( @@ -277,7 +281,24 @@ export function add( level = 0, options: AddNotificationOptions = {}, ): void { - NotificationEvent.dispatch(message, level, options.customTitle); + let details = options.details; + + if (options.response !== undefined) { + details = { + status: options.response.status, + additionalDetails: options.details, + validationErrors: + options.response.status === 422 + ? options.response.body.validationErrors + : undefined, + }; + message = message + ": " + options.response.body.message; + } + + NotificationEvent.dispatch(message, level, { + customTitle: options.customTitle, + details, + }); new Notification( "notification", diff --git a/frontend/src/ts/elements/scroll-to-top.ts b/frontend/src/ts/elements/scroll-to-top.ts index 4eb654f11..2ebd2d48e 100644 --- a/frontend/src/ts/elements/scroll-to-top.ts +++ b/frontend/src/ts/elements/scroll-to-top.ts @@ -1,27 +1,30 @@ import * as ActivePage from "../states/active-page"; import { prefersReducedMotion } from "../utils/misc"; +import { qsr } from "../utils/dom"; let visible = false; +const button = qsr(".scrollToTopButton"); + export function hide(): void { - $(".scrollToTopButton").addClass("invisible"); + button.addClass("invisible"); visible = false; } function show(): void { - $(".scrollToTopButton").removeClass("invisible"); + button.removeClass("invisible"); visible = true; } -$(document).on("click", ".scrollToTopButton", () => { - $(".scrollToTopButton").addClass("invisible"); +button.on("click", () => { + button.addClass("invisible"); window.scrollTo({ top: 0, behavior: prefersReducedMotion() ? "instant" : "smooth", }); }); -$(window).on("scroll", () => { +window.addEventListener("scroll", () => { const page = ActivePage.get(); if (page === "test") return; diff --git a/frontend/src/ts/modals/dev-options.ts b/frontend/src/ts/modals/dev-options.ts index 199f530ab..6c254688a 100644 --- a/frontend/src/ts/modals/dev-options.ts +++ b/frontend/src/ts/modals/dev-options.ts @@ -32,6 +32,7 @@ async function setup(modalEl: HTMLElement): Promise { }); Notifications.add("This is a test", -1, { duration: 0, + details: { test: true, error: "Example error message" }, }); void modal.hide(); }); diff --git a/frontend/src/ts/modals/edit-preset.ts b/frontend/src/ts/modals/edit-preset.ts index 694e9b69f..443df2f24 100644 --- a/frontend/src/ts/modals/edit-preset.ts +++ b/frontend/src/ts/modals/edit-preset.ts @@ -48,7 +48,7 @@ export function show(action: string, id?: string, name?: string): void { $("#editPresetModal .modal .text").addClass("hidden"); addCheckBoxes(); presetNameEl ??= new ValidatedHtmlInputElement( - qsr("#editPresetModal .modal input"), + qsr("#editPresetModal .modal input[type=text]"), { schema: PresetNameSchema, }, @@ -284,7 +284,7 @@ async function apply(): Promise { if (response.status !== 200 || response.body.data === null) { Notifications.add( - "Failed to add preset: " + + "Failed to add preset" + response.body.message.replace(presetName, propPresetName), -1, ); @@ -325,7 +325,7 @@ async function apply(): Promise { }); if (response.status !== 200) { - Notifications.add("Failed to edit preset: " + response.body.message, -1); + Notifications.add("Failed to edit preset", -1, { response }); } else { Notifications.add("Preset updated", 1); @@ -344,10 +344,7 @@ async function apply(): Promise { const response = await Ape.presets.delete({ params: { presetId } }); if (response.status !== 200) { - Notifications.add( - "Failed to remove preset: " + response.body.message, - -1, - ); + Notifications.add("Failed to remove preset", -1, { response }); } else { Notifications.add("Preset removed", 1); snapshotPresets.forEach((preset: SnapshotPreset, index: number) => { diff --git a/frontend/src/ts/modals/edit-profile.ts b/frontend/src/ts/modals/edit-profile.ts index 633c89d39..3eed57085 100644 --- a/frontend/src/ts/modals/edit-profile.ts +++ b/frontend/src/ts/modals/edit-profile.ts @@ -180,7 +180,7 @@ async function updateProfile(): Promise { Loader.hide(); if (response.status !== 200) { - Notifications.add("Failed to update profile: " + response.body.message, -1); + Notifications.add("Failed to update profile", -1, { response }); return; } diff --git a/frontend/src/ts/modals/edit-result-tags.ts b/frontend/src/ts/modals/edit-result-tags.ts index 6ce533a43..f3a65df61 100644 --- a/frontend/src/ts/modals/edit-result-tags.ts +++ b/frontend/src/ts/modals/edit-result-tags.ts @@ -121,10 +121,7 @@ async function save(): Promise { state.tags = state.tags.filter((el) => el !== undefined); if (response.status !== 200) { - Notifications.add( - "Failed to update result tags: " + response.body.message, - -1, - ); + Notifications.add("Failed to update result tags", -1, { response }); return; } diff --git a/frontend/src/ts/modals/edit-tag.ts b/frontend/src/ts/modals/edit-tag.ts index 880531a57..5582aa14d 100644 --- a/frontend/src/ts/modals/edit-tag.ts +++ b/frontend/src/ts/modals/edit-tag.ts @@ -37,6 +37,7 @@ const actionModals: Record = { message: "Failed to add tag: " + response.body.message.replace(tagName, propTagName), + notificationOptions: { response }, }; } @@ -83,7 +84,8 @@ const actionModals: Record = { if (response.status !== 200) { return { status: -1, - message: "Failed to edit tag: " + response.body.message, + message: "Failed to edit tag", + notificationOptions: { response }, }; } @@ -113,7 +115,8 @@ const actionModals: Record = { if (response.status !== 200) { return { status: -1, - message: "Failed to remove tag: " + response.body.message, + message: "Failed to remove tag", + notificationOptions: { response }, }; } @@ -143,7 +146,8 @@ const actionModals: Record = { if (response.status !== 200) { return { status: -1, - message: "Failed to clear tag pb: " + response.body.message, + message: "Failed to clear tag pb", + notificationOptions: { response }, }; } diff --git a/frontend/src/ts/modals/quote-approve.ts b/frontend/src/ts/modals/quote-approve.ts index 988c7c5a3..3fd7a79fe 100644 --- a/frontend/src/ts/modals/quote-approve.ts +++ b/frontend/src/ts/modals/quote-approve.ts @@ -98,7 +98,7 @@ async function getQuotes(): Promise { Loader.hide(); if (response.status !== 200) { - Notifications.add("Failed to get new quotes: " + response.body.message, -1); + Notifications.add("Failed to get new quotes", -1, { response }); return; } @@ -160,7 +160,7 @@ async function approveQuote(index: number, dbid: string): Promise { if (response.status !== 200) { resetButtons(index); quote.find("textarea, input").prop("disabled", false); - Notifications.add("Failed to approve quote: " + response.body.message, -1); + Notifications.add("Failed to approve quote", -1, { response }); return; } @@ -184,7 +184,7 @@ async function refuseQuote(index: number, dbid: string): Promise { if (response.status !== 200) { resetButtons(index); quote.find("textarea, input").prop("disabled", false); - Notifications.add("Failed to refuse quote: " + response.body.message, -1); + Notifications.add("Failed to refuse quote", -1, { response }); return; } @@ -218,7 +218,7 @@ async function editQuote(index: number, dbid: string): Promise { if (response.status !== 200) { resetButtons(index); quote.find("textarea, input").prop("disabled", false); - Notifications.add("Failed to approve quote: " + response.body.message, -1); + Notifications.add("Failed to approve quote", -1, { response }); return; } diff --git a/frontend/src/ts/modals/quote-rate.ts b/frontend/src/ts/modals/quote-rate.ts index 132eef7bf..e22b97f91 100644 --- a/frontend/src/ts/modals/quote-rate.ts +++ b/frontend/src/ts/modals/quote-rate.ts @@ -60,10 +60,7 @@ export async function getQuoteStats( Loader.hide(); if (response.status !== 200) { - Notifications.add( - "Failed to get quote ratings: " + response.body.message, - -1, - ); + Notifications.add("Failed to get quote ratings", -1, { response }); return; } @@ -156,10 +153,7 @@ async function submit(): Promise { Loader.hide(); if (response.status !== 200) { - Notifications.add( - "Failed to submit quote rating: " + response.body.message, - -1, - ); + Notifications.add("Failed to submit quote rating", -1, { response }); return; } diff --git a/frontend/src/ts/modals/quote-report.ts b/frontend/src/ts/modals/quote-report.ts index 991ec10bc..f24a423ab 100644 --- a/frontend/src/ts/modals/quote-report.ts +++ b/frontend/src/ts/modals/quote-report.ts @@ -121,7 +121,7 @@ async function submitReport(): Promise { Loader.hide(); if (response.status !== 200) { - Notifications.add("Failed to report quote: " + response.body.message, -1); + Notifications.add("Failed to report quote", -1, { response }); return; } diff --git a/frontend/src/ts/modals/quote-search.ts b/frontend/src/ts/modals/quote-search.ts index 14e7c043a..ddfacc1bb 100644 --- a/frontend/src/ts/modals/quote-search.ts +++ b/frontend/src/ts/modals/quote-search.ts @@ -28,6 +28,11 @@ const searchServiceCache: Record> = {}; const pageSize = 100; let currentPageNumber = 1; let usingCustomLength = true; +let quotes: Quote[]; + +async function updateQuotes(): Promise { + ({ quotes } = await QuotesController.getQuotes(Config.language)); +} function getSearchService( language: string, @@ -188,10 +193,61 @@ function buildQuoteSearchResult( `; } +function exactSearch(quotes: Quote[], captured: RegExp[]): [Quote[], string[]] { + const matches: Quote[] = []; + const exactSearchQueryTerms: Set = new Set(); + + for (const quote of quotes) { + const textAndSource = quote.text + quote.source; + const currentMatches = []; + let noMatch = false; + + for (const regex of captured) { + const match = textAndSource.match(regex); + + if (!match) { + noMatch = true; + break; + } + + currentMatches.push(match[0]); + } + + if (!noMatch) { + currentMatches.forEach((match) => exactSearchQueryTerms.add(match)); + matches.push(quote); + } + } + + return [matches, Array.from(exactSearchQueryTerms)]; +} + async function updateResults(searchText: string): Promise { if (!modal.isOpen()) return; - const { quotes } = await QuotesController.getQuotes(Config.language); + if (quotes === undefined) { + ({ quotes } = await QuotesController.getQuotes(Config.language)); + } + + let matches: Quote[] = []; + let matchedQueryTerms: string[] = []; + let exactSearchMatches: Quote[] = []; + let exactSearchMatchedQueryTerms: string[] = []; + + const quotationsRegex = /"(.*?)"/g; + const exactSearchQueries = Array.from(searchText.matchAll(quotationsRegex)); + const removedSearchText = searchText.replaceAll(quotationsRegex, ""); + + if (exactSearchQueries[0]) { + const searchQueriesRaw = exactSearchQueries.map( + (query) => new RegExp(query[1] ?? "", "i"), + ); + + [exactSearchMatches, exactSearchMatchedQueryTerms] = exactSearch( + quotes, + searchQueriesRaw, + ); + } const quoteSearchService = getSearchService( Config.language, @@ -200,8 +256,21 @@ async function updateResults(searchText: string): Promise { return `${quote.text} ${quote.id} ${quote.source}`; }, ); - const { results: matches, matchedQueryTerms } = - quoteSearchService.query(searchText); + + if (exactSearchMatches.length > 0 || removedSearchText === searchText) { + const ids = exactSearchMatches.map((match) => match.id); + + ({ results: matches, matchedQueryTerms } = quoteSearchService.query( + removedSearchText, + ids, + )); + + exactSearchMatches.forEach((match) => { + if (!matches.includes(match)) matches.push(match); + }); + + matchedQueryTerms = [...exactSearchMatchedQueryTerms, ...matchedQueryTerms]; + } const quotesToShow = applyQuoteLengthFilter( applyQuoteFavFilter(searchText === "" ? quotes : matches), @@ -340,12 +409,7 @@ export async function show(showOptions?: ShowOptions): Promise { }); }, afterAnimation: async () => { - const quoteSearchInputValue = $( - "#quoteSearchModal input", - ).val() as string; - currentPageNumber = 1; - - void updateResults(quoteSearchInputValue); + void updateQuotes(); }, }); } diff --git a/frontend/src/ts/modals/quote-submit.ts b/frontend/src/ts/modals/quote-submit.ts index 07d0befc7..2a2f99fb3 100644 --- a/frontend/src/ts/modals/quote-submit.ts +++ b/frontend/src/ts/modals/quote-submit.ts @@ -44,7 +44,7 @@ async function submitQuote(): Promise { Loader.hide(); if (response.status !== 200) { - Notifications.add("Failed to submit quote: " + response.body.message, -1); + Notifications.add("Failed to submit quote", -1, { response }); return; } diff --git a/frontend/src/ts/modals/simple-modals.ts b/frontend/src/ts/modals/simple-modals.ts index a15a83135..1bf579504 100644 --- a/frontend/src/ts/modals/simple-modals.ts +++ b/frontend/src/ts/modals/simple-modals.ts @@ -288,7 +288,8 @@ list.updateEmail = new SimpleModal({ if (response.status !== 200) { return { status: -1, - message: "Failed to update email: " + response.body.message, + message: "Failed to update email", + notificationOptions: { response }, }; } @@ -499,13 +500,14 @@ list.updateName = new SimpleModal({ }; } - const updateNameResponse = await Ape.users.updateName({ + const response = await Ape.users.updateName({ body: { name: newName }, }); - if (updateNameResponse.status !== 200) { + if (response.status !== 200) { return { status: -1, - message: "Failed to update name: " + updateNameResponse.body.message, + message: "Failed to update name", + notificationOptions: { response }, }; } @@ -598,7 +600,8 @@ list.updatePassword = new SimpleModal({ if (response.status !== 200) { return { status: -1, - message: "Failed to update password: " + response.body.message, + message: "Failed to update password", + notificationOptions: { response }, }; } @@ -699,8 +702,8 @@ list.addPasswordAuth = new SimpleModal({ return { status: -1, message: - "Password authentication added but updating the database email failed. This shouldn't happen, please contact support. Error: " + - response.body.message, + "Password authentication added but updating the database email failed. This shouldn't happen, please contact support. Error", + notificationOptions: { response }, }; } @@ -735,12 +738,13 @@ list.deleteAccount = new SimpleModal({ } Notifications.add("Deleting all data...", 0); - const usersResponse = await Ape.users.delete(); + const response = await Ape.users.delete(); - if (usersResponse.status !== 200) { + if (response.status !== 200) { return { status: -1, - message: "Failed to delete user data: " + usersResponse.body.message, + message: "Failed to delete user data", + notificationOptions: { response }, }; } @@ -791,7 +795,8 @@ list.resetAccount = new SimpleModal({ if (response.status !== 200) { return { status: -1, - message: "Failed to reset account: " + response.body.message, + message: "Failed to reset account", + notificationOptions: { response }, }; } @@ -837,7 +842,8 @@ list.optOutOfLeaderboards = new SimpleModal({ if (response.status !== 200) { return { status: -1, - message: "Failed to opt out: " + response.body.message, + message: "Failed to opt out", + notificationOptions: { response }, }; } @@ -898,7 +904,8 @@ list.resetPersonalBests = new SimpleModal({ if (response.status !== 200) { return { status: -1, - message: "Failed to reset personal bests: " + response.body.message, + message: "Failed to reset personal bests", + notificationOptions: { response }, }; } @@ -974,7 +981,8 @@ list.revokeAllTokens = new SimpleModal({ if (response.status !== 200) { return { status: -1, - message: "Failed to revoke tokens: " + response.body.message, + message: "Failed to revoke tokens", + notificationOptions: { response }, }; } @@ -1015,7 +1023,8 @@ list.unlinkDiscord = new SimpleModal({ if (response.status !== 200) { return { status: -1, - message: "Failed to unlink Discord: " + response.body.message, + message: "Failed to unlink Discord", + notificationOptions: { response }, }; } @@ -1203,7 +1212,7 @@ list.devGenerateData = new SimpleModal({ const span = document.querySelector( "#devGenerateData_1 + span", ) as HTMLInputElement; - span.innerHTML = `if checked, user will be created with ${target.value}@example.com and password: password`; + span.innerText = `if checked, user will be created with ${target.value}@example.com and password: password`; return; }, validation: { diff --git a/frontend/src/ts/modals/streak-hour-offset.ts b/frontend/src/ts/modals/streak-hour-offset.ts index b81cc48e9..f2f9d90aa 100644 --- a/frontend/src/ts/modals/streak-hour-offset.ts +++ b/frontend/src/ts/modals/streak-hour-offset.ts @@ -88,10 +88,7 @@ async function apply(): Promise { }); Loader.hide(); if (response.status !== 200) { - Notifications.add( - "Failed to set streak hour offset: " + response.body.message, - -1, - ); + Notifications.add("Failed to set streak hour offset", -1, { response }); } else { Notifications.add("Streak hour offset set", 1); const snap = getSnapshot() as Snapshot; diff --git a/frontend/src/ts/modals/user-report.ts b/frontend/src/ts/modals/user-report.ts index f54945928..1878a09a7 100644 --- a/frontend/src/ts/modals/user-report.ts +++ b/frontend/src/ts/modals/user-report.ts @@ -128,7 +128,7 @@ async function submitReport(): Promise { Loader.hide(); if (response.status !== 200) { - Notifications.add("Failed to report user: " + response.body.message, -1); + Notifications.add("Failed to report user", -1, { response }); return; } diff --git a/frontend/src/ts/observables/notification-event.ts b/frontend/src/ts/observables/notification-event.ts index cf05dc18b..5def1fd9a 100644 --- a/frontend/src/ts/observables/notification-event.ts +++ b/frontend/src/ts/observables/notification-event.ts @@ -1,7 +1,12 @@ -type SubscribeFunction = ( +export type NotificationOptions = { + customTitle?: string; + details?: object | string; +}; + +export type SubscribeFunction = ( message: string, level: number, - customTitle?: string, + options: NotificationOptions, ) => void; const subscribers: SubscribeFunction[] = []; @@ -13,11 +18,11 @@ export function subscribe(fn: SubscribeFunction): void { export function dispatch( message: string, level: number, - customTitle?: string, + options: NotificationOptions, ): void { subscribers.forEach((fn) => { try { - fn(message, level, customTitle); + fn(message, level, options); } catch (e) { console.error("Notification event subscriber threw an error"); console.error(e); diff --git a/frontend/src/ts/pages/account.ts b/frontend/src/ts/pages/account.ts index 537725739..326193880 100644 --- a/frontend/src/ts/pages/account.ts +++ b/frontend/src/ts/pages/account.ts @@ -1107,7 +1107,7 @@ $(".pageAccount").on("click", ".miniResultChartButton", async (event) => { target.removeClass("loading"); if (response.status !== 200) { - Notifications.add("Error fetching result: " + response.body.message, -1); + Notifications.add("Error fetching result", -1, { response }); return; } diff --git a/frontend/src/ts/pages/settings.ts b/frontend/src/ts/pages/settings.ts index c46bddad9..1dbd5ca32 100644 --- a/frontend/src/ts/pages/settings.ts +++ b/frontend/src/ts/pages/settings.ts @@ -43,7 +43,7 @@ import * as CustomBackgroundPicker from "../elements/settings/custom-background- import * as CustomFontPicker from "../elements/settings/custom-font-picker"; import * as AuthEvent from "../observables/auth-event"; import * as FpsLimitSection from "../elements/settings/fps-limit-section"; -import { qsr } from "../utils/dom"; +import { qs, qsr } from "../utils/dom"; let settingsInitialized = false; @@ -763,13 +763,28 @@ function toggleSettingsGroup(groupName: string): void { //The highlight is repeated/broken when toggling the group handleHighlightSection(undefined); - const groupEl = $(`.pageSettings .settingsGroup.${groupName}`); - groupEl.stop(true, true).slideToggle(250).toggleClass("slideup"); - if (groupEl.hasClass("slideup")) { + const groupEl = qs(`.pageSettings .settingsGroup.${groupName}`); + if (!groupEl?.hasClass("slideup")) { + groupEl?.animate({ + height: 0, + duration: 250, + onComplete: () => { + groupEl?.hide(); + }, + }); + groupEl?.addClass("slideup"); $(`.pageSettings .sectionGroupTitle[group=${groupName}]`).addClass( "rotateIcon", ); } else { + groupEl?.show(); + groupEl?.setStyle({ height: "" }); + const height = groupEl.getOffsetHeight(); + groupEl?.animate({ + height: [0, height], + duration: 250, + }); + groupEl?.removeClass("slideup"); $(`.pageSettings .sectionGroupTitle[group=${groupName}]`).removeClass( "rotateIcon", ); diff --git a/frontend/src/ts/states/arabic-lazy-mode.ts b/frontend/src/ts/states/arabic-lazy-mode.ts deleted file mode 100644 index 590fd50e5..000000000 --- a/frontend/src/ts/states/arabic-lazy-mode.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { z } from "zod"; -import { LocalStorageWithSchema } from "../utils/local-storage-with-schema"; - -const ls = new LocalStorageWithSchema({ - key: "prefersArabicLazyMode", - schema: z.boolean(), - fallback: true, -}); - -export function get(): boolean { - return ls.get(); -} - -export function set(value: boolean): void { - ls.set(value); -} diff --git a/frontend/src/ts/states/remember-lazy-mode.ts b/frontend/src/ts/states/remember-lazy-mode.ts new file mode 100644 index 000000000..08d04a662 --- /dev/null +++ b/frontend/src/ts/states/remember-lazy-mode.ts @@ -0,0 +1,30 @@ +import { z } from "zod"; +import { LocalStorageWithSchema } from "../utils/local-storage-with-schema"; + +const rememberLazyModeLS = new LocalStorageWithSchema({ + key: "rememberLazyMode", + schema: z.boolean(), + fallback: false, +}); + +const arabicLazyModeLS = new LocalStorageWithSchema({ + key: "prefersArabicLazyMode", + schema: z.boolean(), + fallback: true, +}); + +export function getRemember(): boolean { + return rememberLazyModeLS.get(); +} + +export function setRemember(value: boolean): void { + rememberLazyModeLS.set(value); +} + +export function getArabicPref(): boolean { + return arabicLazyModeLS.get(); +} + +export function setArabicPref(value: boolean): void { + arabicLazyModeLS.set(value); +} diff --git a/frontend/src/ts/states/time.ts b/frontend/src/ts/states/time.ts index 899bf154b..c18761a1a 100644 --- a/frontend/src/ts/states/time.ts +++ b/frontend/src/ts/states/time.ts @@ -4,8 +4,8 @@ export function get(): number { return time; } -export function set(active: number): void { - time = active; +export function set(number: number): void { + time = number; } export function increment(): void { diff --git a/frontend/src/ts/test/alt-tracker.ts b/frontend/src/ts/test/alt-tracker.ts index edf80e459..fa1393392 100644 --- a/frontend/src/ts/test/alt-tracker.ts +++ b/frontend/src/ts/test/alt-tracker.ts @@ -1,7 +1,7 @@ export let leftState = false; export let rightState = false; -$(document).on("keydown", (e) => { +document.addEventListener("keydown", (e: KeyboardEvent) => { if (e.code === "AltLeft") { leftState = true; } else if (e.code === "AltRight") { @@ -9,7 +9,7 @@ $(document).on("keydown", (e) => { } }); -$(document).on("keyup", (e) => { +document.addEventListener("keyup", (e: KeyboardEvent) => { if (e.code === "AltLeft") { leftState = false; } else if (e.code === "AltRight") { diff --git a/frontend/src/ts/test/british-english.ts b/frontend/src/ts/test/british-english.ts index b83122631..b97f3fed7 100644 --- a/frontend/src/ts/test/british-english.ts +++ b/frontend/src/ts/test/british-english.ts @@ -652,6 +652,33 @@ const replacementRules: BritishEnglishReplacements = { moisturizing: "moisturising", favoring: "favouring", marvelous: "marvellous", + hematuria: "haematuria", + hemoptysis: "haemoptysis", + hemorrhoid: "haemorrhoid", + hemorrhagic: "haemorrhagic", + hypercalcemia: "hypercalcaemia", + hyperglycemia: "hyperglycaemia", + hypoglycemia: "hypoglycaemia", + toxemia: "toxaemia", + hypoxemia: "hypoxaemia", + bacteremia: "bacteraemia", + hypernatremia: "hypernatraemia", + hyponatremia: "hyponatraemia", + leukocytosis: "leucocytosis", + leukocyte: "leucocyte", + leukopenia: "leucopenia", + apnea: "apnoea", + bradypnea: "bradypnoea", + tachypnea: "tachypnoea", + orthopnea: "orthopnoea", + ileocecal: "ileocaecal", + metastasize: "metastasise", + lymphedema: "lymphoedema", + neuron: "neurone", + hemianopsia: "hemianopia", + galactorrhea: "galactorrhoea", + nebulizer: "nebuliser", + paresthesia: "paraesthesia", }; export async function replace( diff --git a/frontend/src/ts/test/caps-warning.ts b/frontend/src/ts/test/caps-warning.ts index cb78f72fb..87a7ed847 100644 --- a/frontend/src/ts/test/caps-warning.ts +++ b/frontend/src/ts/test/caps-warning.ts @@ -1,7 +1,8 @@ import Config from "../config"; import * as Misc from "../utils/misc"; +import { qsr } from "../utils/dom"; -const el = document.querySelector("#capsWarning") as HTMLElement; +const el = qsr("#capsWarning"); export let capsState = false; @@ -9,23 +10,23 @@ let visible = false; function show(): void { if (!visible) { - el?.classList.remove("hidden"); + el.removeClass("hidden"); visible = true; } } function hide(): void { if (visible) { - el?.classList.add("hidden"); + el.addClass("hidden"); visible = false; } } -function update(event: JQuery.KeyDownEvent | JQuery.KeyUpEvent): void { - if (event?.originalEvent?.key === "CapsLock" && capsState !== null) { +function update(event: KeyboardEvent): void { + if (event.key === "CapsLock" && capsState !== null) { capsState = !capsState; } else { - const modState = event?.originalEvent?.getModifierState?.("CapsLock"); + const modState = event.getModifierState?.("CapsLock"); if (modState !== undefined) { capsState = modState; } @@ -40,8 +41,8 @@ function update(event: JQuery.KeyDownEvent | JQuery.KeyUpEvent): void { } catch {} } -$(document).on("keyup", update); +document.addEventListener("keyup", update); -$(document).on("keydown", (event) => { +document.addEventListener("keydown", (event) => { if (Misc.isMac()) update(event); }); diff --git a/frontend/src/ts/test/shift-tracker.ts b/frontend/src/ts/test/shift-tracker.ts index d60dad90a..90a7feea2 100644 --- a/frontend/src/ts/test/shift-tracker.ts +++ b/frontend/src/ts/test/shift-tracker.ts @@ -4,7 +4,7 @@ import * as KeyConverter from "../utils/key-converter"; export let leftState = false; export let rightState = false; -$(document).on("keydown", (e) => { +document.addEventListener("keydown", (e: KeyboardEvent) => { if (e.code === "ShiftLeft") { leftState = true; rightState = false; @@ -14,7 +14,7 @@ $(document).on("keydown", (e) => { } }); -$(document).on("keyup", (e) => { +document.addEventListener("keyup", (e: KeyboardEvent) => { if (e.code === "ShiftLeft" || e.code === "ShiftRight") { leftState = false; rightState = false; diff --git a/frontend/src/ts/test/test-logic.ts b/frontend/src/ts/test/test-logic.ts index 85634bc4e..b760253ed 100644 --- a/frontend/src/ts/test/test-logic.ts +++ b/frontend/src/ts/test/test-logic.ts @@ -44,7 +44,7 @@ import * as Tribe from "../tribe/tribe"; import * as TribeTypes from "../tribe/types"; import * as ConnectionState from "../states/connection"; import * as KeymapEvent from "../observables/keymap-event"; -import * as ArabicLazyMode from "../states/arabic-lazy-mode"; +import * as LazyModeState from "../states/remember-lazy-mode"; import Format from "../utils/format"; import { QuoteLength, QuoteLengthConfig } from "@monkeytype/schemas/configs"; import { Mode } from "@monkeytype/schemas/shared"; @@ -75,6 +75,7 @@ import { animate } from "animejs"; import { setInputElementValue } from "../input/input-element"; import { debounce } from "throttle-debounce"; import tribeSocket from "../tribe/tribe-socket"; +import * as Time from "../states/time"; let resolve: TribeTypes.ResultResolve = {}; let failReason = ""; @@ -108,6 +109,7 @@ export function startTest(now: number): boolean { Replay.startReplayRecording(); Replay.replayGetWordsList(TestWords.words.list); TestInput.resetKeypressTimings(); + Time.set(0); TestTimer.clear(); for (const fb of getActiveFunboxesWithFunction("start")) { @@ -356,7 +358,6 @@ export function restart(options = {} as RestartOptions): void { } let lastInitError: Error | null = null; -let rememberLazyMode: boolean; let showedLazyModeNotification: boolean = false; let testReinitCount = 0; @@ -440,39 +441,43 @@ async function init(): Promise { .some((lang) => !lang.noLazyMode); if (Config.lazyMode && !anySupportsLazyMode) { - rememberLazyMode = true; - Notifications.add( - "None of the selected polyglot languages support lazy mode.", - 0, - { - important: true, - }, - ); + LazyModeState.setRemember(true); + if (!showedLazyModeNotification) { + Notifications.add( + "None of the selected polyglot languages support lazy mode.", + 0, + { + important: true, + }, + ); + showedLazyModeNotification = true; + } setConfig("lazyMode", false); - } else if (rememberLazyMode && anySupportsLazyMode) { - setConfig("lazyMode", true, { - nosave: true, - }); + } else if (LazyModeState.getRemember() && anySupportsLazyMode) { + setConfig("lazyMode", true); + LazyModeState.setRemember(false); + showedLazyModeNotification = false; } } else { // normal mode if (Config.lazyMode && !allowLazyMode) { - rememberLazyMode = true; - showedLazyModeNotification = true; - Notifications.add("This language does not support lazy mode.", 0, { - important: true, - }); - + LazyModeState.setRemember(true); + if (!showedLazyModeNotification) { + Notifications.add("This language does not support lazy mode.", 0, { + important: true, + }); + showedLazyModeNotification = true; + } setConfig("lazyMode", false); - } else if (rememberLazyMode && !language.noLazyMode) { - setConfig("lazyMode", true, { - nosave: true, - }); + } else if (LazyModeState.getRemember() && allowLazyMode) { + setConfig("lazyMode", true); + LazyModeState.setRemember(false); + showedLazyModeNotification = false; } } if (!Config.lazyMode && !language.noLazyMode) { - rememberLazyMode = false; + LazyModeState.setRemember(false); } if (Config.mode === "custom") { @@ -1413,7 +1418,7 @@ async function saveResult( response.body.message = "Looks like your result data is using an incorrect schema. Please refresh the page to download the new update. If the problem persists, please contact support."; } - Notifications.add("Failed to save result: " + response.body.message, -1); + Notifications.add("Failed to save result", -1, { response }); return; } @@ -1725,7 +1730,10 @@ ConfigEvent.subscribe(({ key, newValue, nosave }) => { if (ActivePage.get() === "test") { if (key === "language") { //automatically enable lazy mode for arabic - if ((newValue as string)?.startsWith("arabic") && ArabicLazyMode.get()) { + if ( + (newValue as string)?.startsWith("arabic") && + LazyModeState.getArabicPref() + ) { setConfig("lazyMode", true, { nosave: true, }); @@ -1758,13 +1766,7 @@ ConfigEvent.subscribe(({ key, newValue, nosave }) => { } if (key === "lazyMode" && !nosave) { if (Config.language.startsWith("arabic")) { - ArabicLazyMode.set(newValue); - } - if (newValue) { - if (!showedLazyModeNotification) { - rememberLazyMode = false; - } - showedLazyModeNotification = false; + LazyModeState.setArabicPref(newValue); } } }); diff --git a/frontend/src/ts/test/test-timer.ts b/frontend/src/ts/test/test-timer.ts index 876f98625..e30f6b393 100644 --- a/frontend/src/ts/test/test-timer.ts +++ b/frontend/src/ts/test/test-timer.ts @@ -62,7 +62,6 @@ export function enableTimerDebug(): void { export function clear(): void { clearLowFpsMode(); - Time.set(0); newTimer.reset(); if (timer !== null) clearTimeout(timer); } diff --git a/frontend/src/ts/utils/caret.ts b/frontend/src/ts/utils/caret.ts index 60c603c48..47408db78 100644 --- a/frontend/src/ts/utils/caret.ts +++ b/frontend/src/ts/utils/caret.ts @@ -300,7 +300,7 @@ export class Caret { if (options.letterIndex >= letters.length) { side = "afterLetter"; - if (Config.blindMode) { + if (Config.blindMode || Config.hideExtraLetters) { options.letterIndex = wordText?.length - 1; } else { options.letterIndex = letters.length - 1; @@ -450,6 +450,9 @@ export class Caret { let left = 0; let top = 0; + const tapeOffset = + wordsWrapperCache.getOffsetWidth() * (Config.tapeMargin / 100); + // yes, this is all super verbose, but its easier to maintain and understand if (isWordRTL) { let afterLetterCorrection = 0; @@ -475,8 +478,7 @@ export class Caret { left += options.letter.getOffsetLeft(); left += afterLetterCorrection; if (this.isMainCaret && lockedMainCaretInTape) { - left += - wordsWrapperCache.getOffsetWidth() * (Config.tapeMargin / 100); + left += wordsWrapperCache.getOffsetWidth() - tapeOffset; } else { left += options.word.getOffsetLeft(); left += options.word.getOffsetWidth(); @@ -486,8 +488,7 @@ export class Caret { left += width * -1; } if (this.isMainCaret && lockedMainCaretInTape) { - left += - wordsWrapperCache.getOffsetWidth() * (Config.tapeMargin / 100); + left += wordsWrapperCache.getOffsetWidth() - tapeOffset; } else { left += options.letter.getOffsetLeft(); left += options.word.getOffsetLeft(); @@ -508,15 +509,13 @@ export class Caret { left += options.letter.getOffsetLeft(); left += afterLetterCorrection; if (this.isMainCaret && lockedMainCaretInTape) { - left += - wordsWrapperCache.getOffsetWidth() * (Config.tapeMargin / 100); + left += tapeOffset; } else { left += options.word.getOffsetLeft(); } } else if (Config.tapeMode === "letter") { if (this.isMainCaret && lockedMainCaretInTape) { - left += - wordsWrapperCache.getOffsetWidth() * (Config.tapeMargin / 100); + left += tapeOffset; } else { left += options.letter.getOffsetLeft(); left += options.word.getOffsetLeft(); diff --git a/frontend/src/ts/utils/dom.ts b/frontend/src/ts/utils/dom.ts index 025b7b049..14872fcfd 100644 --- a/frontend/src/ts/utils/dom.ts +++ b/frontend/src/ts/utils/dom.ts @@ -105,6 +105,12 @@ type ElementWithValue = | HTMLTextAreaElement | HTMLSelectElement; +export type DomUtilsEvent = Omit; + +type DomUtilsEventListenerOrEventListenerObject = + | { (evt: DomUtilsEvent): void } + | { handleEvent(object: DomUtilsEvent): void }; + export class ElementWithUtils { /** * The native dom element @@ -238,19 +244,19 @@ export class ElementWithUtils { */ on( event: K, - handler: (this: T, ev: HTMLElementEventMap[K]) => void, + handler: (this: T, ev: DomUtilsEvent) => void, ): this; - on(event: string, handler: EventListenerOrEventListenerObject): this; + on(event: string, handler: DomUtilsEventListenerOrEventListenerObject): this; on( event: keyof HTMLElementEventMap | string, handler: - | EventListenerOrEventListenerObject - | ((this: T, ev: Event) => void), + | DomUtilsEventListenerOrEventListenerObject + | ((this: T, ev: DomUtilsEvent) => void), ): this { // this type was some AI magic but if it works it works this.native.addEventListener( event, - handler as EventListenerOrEventListenerObject, + handler as DomUtilsEventListenerOrEventListenerObject, ); return this; } @@ -262,19 +268,22 @@ export class ElementWithUtils { onChild( event: K, query: string, - handler: (this: HTMLElement, ev: HTMLElementEventMap[K]) => void, + handler: ( + this: HTMLElement, + ev: DomUtilsEvent, + ) => void, ): this; onChild( event: string, query: string, - handler: EventListenerOrEventListenerObject, + handler: DomUtilsEventListenerOrEventListenerObject, ): this; onChild( event: keyof HTMLElementEventMap | string, query: string, handler: - | EventListenerOrEventListenerObject - | ((this: HTMLElement, ev: Event) => void), + | DomUtilsEventListenerOrEventListenerObject + | ((this: HTMLElement, ev: DomUtilsEvent) => void), ): this { // this type was some AI magic but if it works it works this.native.addEventListener(event, (e) => { @@ -671,14 +680,14 @@ export class ElementsWithUtils< */ on( event: K, - handler: (this: T, ev: HTMLElementEventMap[K]) => void, + handler: (this: T, ev: DomUtilsEvent) => void, ): this; - on(event: string, handler: EventListenerOrEventListenerObject): this; + on(event: string, handler: DomUtilsEventListenerOrEventListenerObject): this; on( event: keyof HTMLElementEventMap | string, handler: - | EventListenerOrEventListenerObject - | ((this: T, ev: Event) => void), + | DomUtilsEventListenerOrEventListenerObject + | ((this: T, ev: DomUtilsEvent) => void), ): this { for (const item of this) { item.on(event, handler); diff --git a/frontend/src/ts/utils/results.ts b/frontend/src/ts/utils/results.ts index fc10e6ba3..2b4806f67 100644 --- a/frontend/src/ts/utils/results.ts +++ b/frontend/src/ts/utils/results.ts @@ -14,10 +14,7 @@ export async function syncNotSignedInLastResult(uid: string): Promise { body: { result: notSignedInLastResult }, }); if (response.status !== 200) { - Notifications.add( - "Failed to save last result: " + response.body.message, - -1, - ); + Notifications.add("Failed to save last result", -1, { response }); return; } diff --git a/frontend/src/ts/utils/search-service.ts b/frontend/src/ts/utils/search-service.ts index 2773f3c29..8a57203fd 100644 --- a/frontend/src/ts/utils/search-service.ts +++ b/frontend/src/ts/utils/search-service.ts @@ -2,7 +2,7 @@ import { stemmer } from "stemmer"; import levenshtein from "damerau-levenshtein"; export type SearchService = { - query: (query: string) => SearchResult; + query: (query: string, ids: number[]) => SearchResult; }; type SearchServiceOptions = { @@ -110,7 +110,7 @@ export const buildSearchService = ( const tokenSet = Object.keys(reverseIndex); - const query = (searchQuery: string): SearchResult => { + const query = (searchQuery: string, ids: number[]): SearchResult => { const searchResult: SearchResult = { results: [], matchedQueryTerms: [], @@ -155,7 +155,13 @@ export const buildSearchService = ( const scoreForToken = score * idf * termFrequency; - results.set(document.id, currentScore + scoreForToken); + const quote = documents[document.id] as InternalDocument; + if ( + ids.length === 0 || + (quote !== null && quote !== undefined && ids.includes(quote.id)) + ) { + results.set(document.id, currentScore + scoreForToken); + } }); normalizedTokenToOriginal[token]?.forEach((originalToken) => { diff --git a/frontend/src/ts/utils/url-handler.ts b/frontend/src/ts/utils/url-handler.ts index a2d36aaf8..1d5e39d91 100644 --- a/frontend/src/ts/utils/url-handler.ts +++ b/frontend/src/ts/utils/url-handler.ts @@ -49,7 +49,7 @@ export async function linkDiscord(hashOverride: string): Promise { Loader.hide(); if (response.status !== 200) { - Notifications.add("Failed to link Discord: " + response.body.message, -1); + Notifications.add("Failed to link Discord", -1, { response }); return; } diff --git a/frontend/static/languages/bulgarian.json b/frontend/static/languages/bulgarian.json index 5776c70e6..0f0ced240 100644 --- a/frontend/static/languages/bulgarian.json +++ b/frontend/static/languages/bulgarian.json @@ -1,5 +1,9 @@ { "name": "bulgarian", + "rightToLeft": false, + "ligatures": false, + "orderedByFrequency": false, + "bcp47": "bg", "noLazyMode": true, "words": [ "а", diff --git a/frontend/static/languages/bulgarian_1k.json b/frontend/static/languages/bulgarian_1k.json new file mode 100644 index 000000000..40235cfea --- /dev/null +++ b/frontend/static/languages/bulgarian_1k.json @@ -0,0 +1,1020 @@ +{ + "name": "bulgarian_1k", + "rightToLeft": false, + "ligatures": false, + "orderedByFrequency": false, + "bcp47": "bg", + "noLazyMode": true, + "words": [ + "а", + "август", + "автобус", + "автовоз", + "автоматичен", + "автомобил", + "автомобилен", + "автосалон", + "агония", + "адрес", + "аз", + "ако", + "активен", + "актьор", + "актьори", + "ами", + "ангел", + "април", + "асанасьор", + "асистент", + "асоциация", + "асфалт", + "атака", + "атаки", + "атакувам", + "атакуван", + "аудио", + "баба", + "бавен", + "балон", + "банка", + "баня", + "барабан", + "баскетбол", + "баща", + "бе", + "бебе", + "без", + "бездомник", + "безсмислен", + "бесило", + "беше", + "би", + "бил", + "билет", + "било", + "бира", + "битка", + "бих", + "благ", + "благодарност", + "благодаря", + "благоденствие", + "благоухание", + "ближен", + "близо", + "блок", + "богат", + "боли", + "болка", + "боровинка", + "боя", + "брат", + "братя", + "буря", + "бутилка", + "бутон", + "бъде", + "бърз", + "бял", + "бях", + "в", + "вагон", + "важен", + "ваза", + "вампир", + "вана", + "вар", + "варовик", + "вас", + "веднага", + "весел", + "вест", + "вече", + "вечер", + "взимам", + "ви", + "видим", + "видя", + "вие", + "виж", + "вилица", + "винаги", + "вино", + "виновен", + "виновна", + "виновното", + "висота", + "вихрушка", + "вихър", + "вкус", + "вкъщи", + "влак", + "власт", + "влезе", + "вода", + "воден", + "воденица", + "война", + "войник", + "волейбол", + "восък", + "враг", + "врата", + "време", + "връзка", + "връх", + "все", + "всеки", + "вселена", + "всички", + "всичко", + "всъщност", + "вход", + "вцепенен", + "вчера", + "във", + "въздух", + "въпрос", + "вятър", + "галерия", + "гащи", + "гел", + "ги", + "гирлянд", + "глагол", + "глад", + "глас", + "гласовит", + "глоба", + "глобален", + "глух", + "го", + "говоря", + "години", + "гол", + "голям", + "голямо", + "гора", + "гост", + "готвач", + "град", + "градина", + "грамота", + "граница", + "грешка", + "грозде", + "група", + "гръб", + "гръмотевица", + "гума", + "гърмя", + "да", + "дажба", + "дай", + "далече", + "дали", + "данък", + "дар", + "дарявам", + "дата", + "дванадесет", + "дванайсет", + "две", + "двор", + "дворец", + "девет", + "дело", + "делфин", + "ден", + "десет", + "дете", + "деца", + "диван", + "династия", + "днес", + "до", + "добавка", + "добавям", + "добър", + "довод", + "дойде", + "доказателство", + "докато", + "дол", + "долен", + "дом", + "домат", + "допир", + "дори", + "доста", + "достатъчно", + "достъп", + "доя", + "дреха", + "друг", + "друго", + "думa", + "дупка", + "душа", + "дъвка", + "дъга", + "дъжд", + "дълбок", + "дървета", + "дърво", + "държава", + "дюнер", + "дядо", + "дял", + "е", + "един", + "единадесет", + "единайсет", + "една", + "едно", + "езеро", + "език", + "ей", + "екип", + "екран", + "ела", + "електронен", + "електроника", + "епизод", + "есен", + "етаж", + "ето", + "жаба", + "жаден", + "жажда", + "жена", + "жив", + "живот", + "животно", + "жълт", + "за", + "завеса", + "завод", + "завъртам", + "задача", + "заедно", + "заек", + "закон", + "зала", + "залеж", + "залез", + "заливам", + "залязвам", + "запад", + "запалвам", + "заплаха", + "зар", + "заради", + "затова", + "зашивам", + "зашит", + "защипвам", + "защитавам", + "защо", + "защото", + "заявка", + "звук", + "звяр", + "здание", + "здраве", + "здравей", + "зеле", + "зелен", + "зима", + "зла", + "злато", + "зло", + "злоба", + "змей", + "змия", + "знае", + "знаете", + "знаеш", + "знам", + "знание", + "значи", + "зная", + "зъб", + "и", + "ивица", + "ивици", + "иглолистно", + "игра", + "играчи", + "играя", + "идея", + "избор", + "изглежда", + "излезе", + "излизам", + "изложба", + "изложение", + "изпратен", + "изпращам", + "изход", + "или", + "им", + "има", + "имам", + "имаме", + "имате", + "имаш", + "име", + "имена", + "инат", + "инструмент", + "иска", + "истина", + "история", + "йод", + "кабел", + "каза", + "казах", + "казвам", + "как", + "каква", + "какво", + "каквото", + "както", + "какъв", + "камера", + "камион", + "капачка", + "карта", + "картон", + "картоф", + "категория", + "катерица", + "като", + "кафе", + "квадрат", + "квартал", + "киви", + "китара", + "кифла", + "кифли", + "кифличка", + "кифлички", + "клавиатура", + "клавиш", + "клас", + "клетка", + "клон", + "ключ", + "книга", + "когато", + "което", + "кожа", + "които", + "кой", + "който", + "кокошка", + "кола", + "колко", + "колона", + "коляно", + "кон", + "конституция", + "континент", + "копая", + "копие", + "копирам", + "кораб", + "коса", + "котка", + "която", + "крава", + "крави", + "крак", + "кралица", + "кран", + "красив", + "крещя", + "кръв", + "кръг", + "крясък", + "култура", + "курс", + "кутия", + "куче", + "къде", + "където", + "към", + "къща", + "лале", + "лампа", + "ламя", + "легло", + "лежа", + "леля", + "лепенка", + "лесен", + "леха", + "леща", + "ли", + "лисица", + "листо", + "литература", + "лица", + "лице", + "лоби", + "логика", + "логичен", + "лодка", + "локално", + "лук", + "луна", + "лъв", + "лъжица", + "любимец", + "лято", + "магазин", + "магистрала", + "майка", + "маймуна", + "макарон", + "малина", + "малко", + "мама", + "мантия", + "мантията", + "маса", + "масаж", + "масивен", + "масло", + "масово", + "математика", + "матрак", + "мафия", + "мач", + "мащаб", + "мащабен", + "мая", + "ме", + "мед", + "медал", + "между", + "мен", + "мента", + "меса", + "месец", + "месо", + "мечта", + "ми", + "минавам", + "миниатюра", + "минута", + "мирис", + "мисля", + "мляко", + "мляскам", + "много", + "мога", + "могила", + "мода", + "може", + "мокра", + "молба", + "молив", + "моля", + "момент", + "момиче", + "момче", + "море", + "морков", + "морфема", + "мост", + "моя", + "му", + "музика", + "музикален", + "муха", + "място", + "мяу", + "на", + "навик", + "навреме", + "навън", + "над", + "надлез", + "надпис", + "наистина", + "накит", + "нали", + "намек", + "намери", + "намерих", + "наметвам", + "наметна", + "намеци", + "намирам", + "наоколо", + "направи", + "направя", + "напред", + "нар", + "наред", + "нарцис", + "нарязан", + "нас", + "насекоми", + "насекомо", + "насип", + "наставник", + "натиск", + "нахален", + "находка", + "начин", + "не", + "небе", + "небостъргач", + "него", + "негови", + "неговите", + "нежен", + "нежна", + "неин", + "нейн", + "нека", + "нектар", + "неща", + "нещо", + "нея", + "неясен", + "ни", + "нива", + "ние", + "никога", + "никой", + "нито", + "нищо", + "но", + "нов", + "новатор", + "новина", + "нож", + "номер", + "нос", + "носорог", + "нощ", + "нужда", + "някой", + "няколко", + "ням", + "няма", + "нямам", + "обичам", + "обработвам", + "обработка", + "обратен", + "обратно", + "обръщам", + "общ", + "общество", + "обяд", + "овощна", + "огън", + "ода", + "океан", + "око", + "опа", + "опит", + "ориз", + "осем", + "остров", + "островен", + "острови", + "от", + "отговор", + "отново", + "отрасъл", + "отрицание", + "отрова", + "очаквам", + "очи", + "още", + "паваж", + "пазар", + "пак", + "палеж", + "паница", + "пари", + "парк", + "пасивен", + "пате", + "патица", + "перон", + "песен", + "пет", + "печка", + "пеш", + "пея", + "пиеса", + "пиле", + "пингвин", + "пирамида", + "писан", + "писател", + "писмен", + "писменост", + "писмо", + "писък", + "писъци", + "питка", + "плаж", + "план", + "планета", + "планетариум", + "платно", + "плача", + "плет", + "плещи", + "плещите", + "плискам", + "плитка", + "плод", + "пляскам", + "по", + "повече", + "повечето", + "поглед", + "под", + "подаден", + "подем", + "позор", + "покривка", + "поле", + "полет", + "политика", + "полицай", + "полиция", + "полюс", + "помогнаха", + "помощ", + "популярен", + "поради", + "портал", + "посев", + "после", + "посока", + "посочвам", + "почасов", + "поща", + "право", + "правя", + "празен", + "празник", + "прасе", + "прах", + "прашен", + "пращам", + "превня", + "пред", + "предвид", + "преди", + "предмет", + "представител", + "предстои", + "предстоя", + "през", + "президент", + "премествам", + "пренебрегвам", + "пренебрежение", + "при", + "прибирам", + "прикритие", + "прилагателно", + "приличам", + "приличаш", + "пример", + "принтер", + "природа", + "пристрастен", + "пришивам", + "приятел", + "проблем", + "провеждам", + "прозорец", + "производител", + "пролет", + "промяна", + "прост", + "просто", + "простор", + "простота", + "професионалист", + "професия", + "професор", + "прошепвам", + "прошепнеш", + "пръст", + "прът", + "пръчка", + "птица", + "птици", + "пускам", + "пчела", + "първи", + "път", + "пътека", + "пътентранспорт", + "работа", + "равно", + "радиатор", + "радост", + "разбирам", + "развод", + "разговор", + "разказ", + "размер", + "разстройство", + "разтвор", + "разходка", + "рай", + "рак", + "рана", + "ранен", + "ранени", + "раница", + "рано", + "ред", + "режа", + "резба", + "река", + "релса", + "репичка", + "република", + "ресторант", + "решение", + "риба", + "род", + "роза", + "розова", + "розово", + "рокля", + "рояк", + "ръка", + "рядко", + "рядкост", + "рядък", + "с", + "са", + "сам", + "сама", + "само", + "светкавица", + "свещ", + "свобода", + "свързвам", + "свят", + "сграда", + "се", + "себе", + "сега", + "седем", + "седя", + "село", + "селски", + "семейство", + "сено", + "си", + "сигурен", + "сила", + "син", + "скалп", + "скачам", + "скелет", + "скеч", + "скоро", + "скорост", + "скоростен", + "скорпион", + "скъпа", + "сладко", + "след", + "слепец", + "случи", + "слънце", + "сляп", + "сме", + "смъквам", + "смърт", + "снеговалеж", + "снежен", + "снежинка", + "сняг", + "сол", + "спане", + "списък", + "спомен", + "спорт", + "сравнение", + "сравни", + "среща", + "става", + "ставам", + "стан", + "стар", + "стара", + "старост", + "стая", + "сте", + "стена", + "стене", + "стига", + "стикер", + "сто", + "стой", + "стол", + "страна", + "страст", + "страх", + "стрелям", + "струна", + "студ", + "супермаркет", + "сутрин", + "съдържание", + "съжалявам", + "съм", + "сън", + "сърце", + "със", + "състезавам", + "състезание", + "също", + "сяра", + "тава", + "таван", + "тази", + "тайна", + "така", + "там", + "танц", + "тате", + "татко", + "твърд", + "те", + "теб", + "тези", + "телевизия", + "телевизор", + "телефон", + "тенджера", + "тенджери", + "тетрадка", + "тефтер", + "техен", + "ти", + "тиган", + "тих", + "тихо", + "тича", + "тичам", + "това", + "тогава", + "този", + "той", + "толкова", + "тор", + "тост", + "точно", + "трактор", + "трамвай", + "три", + "тринадесет", + "тринайсет", + "триъгълник", + "труден", + "трябва", + "тук", + "тя", + "тяло", + "тях", + "уважение", + "уединение", + "украса", + "украсявам", + "ум", + "уплаха", + "уплашен", + "упражнение", + "ура", + "уред", + "усет", + "успех", + "успешен", + "устройство", + "утре", + "ухо", + "ученик", + "ученичка", + "училище", + "учител", + "файл", + "фактура", + "фараон", + "фенер", + "филм", + "финтирам", + "фитнес", + "флаг", + "фланелка", + "флейта", + "фокус", + "форма", + "фрактура", + "футбол", + "хайде", + "харесвам", + "хладилник", + "хляб", + "ход", + "хор", + "хора", + "хората", + "хотел", + "храст", + "хризантема", + "художник", + "цар", + "царевица", + "царица", + "цвете", + "цвят", + "цел", + "целина", + "цена", + "ценен", + "ценностс", + "църква", + "чайка", + "чакай", + "чакайте", + "чар", + "чаршаф", + "час", + "часовник", + "чаша", + "че", + "чепка", + "червена", + "червено", + "червеното", + "чест", + "често", + "четири", + "четиринадесет", + "четиринайсет", + "четка", + "чешма", + "числа", + "числително", + "число", + "чист", + "чистач", + "човек", + "чорапогащник", + "чужбина", + "чук", + "чучур", + "чушка", + "шанс", + "шепот", + "шептя", + "шест", + "широколистно", + "шише", + "шия", + "шкаф", + "шпатула", + "щастие", + "ще", + "щора", + "юг", + "юли", + "юни", + "юрган", + "ютия", + "я", + "ябълка", + "ягода", + "яде", + "ядене", + "яйце", + "яма", + "ями", + "яре", + "ярост", + "ярък", + "ясен", + "ястреб", + "ято" + ] +} diff --git a/frontend/static/languages/bulgarian_latin.json b/frontend/static/languages/bulgarian_latin.json index 5dc11a2c7..abe7937e8 100644 --- a/frontend/static/languages/bulgarian_latin.json +++ b/frontend/static/languages/bulgarian_latin.json @@ -1,5 +1,9 @@ { "name": "bulgarian_latin", + "rightToLeft": false, + "ligatures": false, + "orderedByFrequency": false, + "bcp47": "bg", "noLazyMode": true, "words": [ "a", diff --git a/frontend/static/languages/bulgarian_latin_1k.json b/frontend/static/languages/bulgarian_latin_1k.json new file mode 100644 index 000000000..cedc09b47 --- /dev/null +++ b/frontend/static/languages/bulgarian_latin_1k.json @@ -0,0 +1,1017 @@ +{ + "name": "bulgarian_latin_1k", + "rightToLeft": false, + "ligatures": false, + "orderedByFrequency": false, + "bcp47": "bg", + "noLazyMode": true, + "words": [ + "a", + "adres", + "agoniya", + "ako", + "aktiori", + "aktiven", + "aktor", + "ami", + "angel", + "april", + "asansior", + "asfalt", + "asistent", + "asociaciya", + "ataka", + "ataki", + "atakuvam", + "atakuvan", + "audio", + "avgust", + "avtobus", + "avtomatichen", + "avtomobil", + "avtomobilen", + "avtosalon", + "avtovoz", + "az", + "baba", + "balon", + "banka", + "banya", + "baraban", + "bashta", + "basketbol", + "baven", + "be", + "bebe", + "beshe", + "besilo", + "bez", + "bezdomnik", + "bezsmislen", + "bi", + "bih", + "bil", + "bilet", + "bilo", + "bira", + "bitka", + "blag", + "blagodarnost", + "blagodarq", + "blagodenstvie", + "blagouhanie", + "blizhen", + "blizo", + "blok", + "bogat", + "boli", + "bolka", + "borovinka", + "boya", + "bqh", + "brat", + "bratya", + "bude", + "burya", + "burz", + "butilka", + "buton", + "byal", + "car", + "carevica", + "carica", + "carkva", + "cel", + "celina", + "cena", + "cenen", + "cennosts", + "chakai", + "chakayte", + "char", + "charshaf", + "chas", + "chasha", + "chasovnik", + "chayka", + "che", + "chepka", + "chervena", + "cherveno", + "chervenoto", + "cheshma", + "chest", + "chesto", + "chetiri", + "chetirinadeset", + "chetirinayset", + "chetka", + "chisla", + "chislitelno", + "chislo", + "chist", + "chistach", + "chorapogashtnik", + "chovek", + "chuchur", + "chujbina", + "chuk", + "chushka", + "cvete", + "cvyat", + "da", + "dai", + "dajd", + "dalbok", + "daleche", + "dali", + "danuk", + "dar", + "darveta", + "daryavam", + "darzhava", + "data", + "davka", + "dazhba", + "deca", + "delfin", + "delo", + "den", + "deset", + "dete", + "devet", + "dinastiya", + "divan", + "dnes", + "do", + "dobavka", + "dobavyam", + "dobur", + "doide", + "dokato", + "dokazatelstvo", + "dol", + "dolen", + "dom", + "domat", + "dopir", + "dori", + "dosta", + "dostap", + "dostatuchno", + "dovod", + "doya", + "dreha", + "drug", + "drugo", + "duga", + "duma", + "dupka", + "durvo", + "dusha", + "dvanadeset", + "dvanayset", + "dve", + "dvor", + "dvorec", + "dyado", + "dyal", + "dyuner", + "e", + "edin", + "edinadeset", + "edinayset", + "edna", + "edno", + "ei", + "ekip", + "ekran", + "ela", + "elektronika", + "epizod", + "esen", + "etaj", + "eto", + "ezero", + "ezik", + "faktura", + "faraon", + "fayl", + "fener", + "film", + "fintiram", + "fitnes", + "flag", + "flanelka", + "fleyta", + "fokus", + "forma", + "fraktura", + "futbol", + "galeriya", + "gashti", + "gel", + "gi", + "girlyand", + "glad", + "glagol", + "glas", + "glasovit", + "globa", + "globalen", + "gluh", + "go", + "godini", + "gol", + "golyam", + "golyamo", + "gora", + "gost", + "gotvach", + "govorq", + "grad", + "gradina", + "gramota", + "gramotevica", + "granica", + "greshka", + "grozde", + "grub", + "grupa", + "guma", + "gurmq", + "haide", + "haresvam", + "hladilnik", + "hlyab", + "hod", + "hor", + "hora", + "horata", + "hotel", + "hrast", + "hrizantema", + "hudozhnik", + "i", + "ideya", + "iglolistno", + "igra", + "igrachi", + "igraya", + "ili", + "im", + "ima", + "imam", + "imame", + "imash", + "imate", + "ime", + "imena", + "inat", + "instrument", + "iska", + "istina", + "istoriya", + "ivica", + "ivici", + "izbor", + "izglejda", + "izhod", + "izleze", + "izlizam", + "izlozhba", + "izlozhenie", + "izprashtam", + "izpraten", + "jaba", + "jaden", + "jena", + "jiv", + "jivot", + "kabel", + "kadeto", + "kafe", + "kak", + "kakto", + "kakuv", + "kakva", + "kakvo", + "kakvoto", + "kamera", + "kamion", + "kapachka", + "karta", + "kartof", + "karton", + "kashta", + "kategoriya", + "katerica", + "kato", + "kaza", + "kazah", + "kazvam", + "kifla", + "kifli", + "kiflichka", + "kiflichki", + "kitara", + "kivi", + "klas", + "klaviatura", + "klavish", + "kletka", + "klon", + "klyuch", + "kniga", + "koeto", + "kogato", + "koi", + "koito", + "koja", + "kokoshka", + "kola", + "kolko", + "kolona", + "kolyano", + "kon", + "konstituciya", + "kontinent", + "kopaya", + "kopie", + "kopiram", + "koqto", + "korab", + "kosa", + "kotka", + "krag", + "krak", + "kralica", + "kran", + "krasiv", + "krava", + "kravi", + "kreshtya", + "kruv", + "kryasyk", + "kuche", + "kude", + "kultura", + "kum", + "kurs", + "kutiya", + "kvadrat", + "kvartal", + "lale", + "lampa", + "lamya", + "lav", + "lazhica", + "leglo", + "leha", + "lelya", + "lepenka", + "lesen", + "leshta", + "lezha", + "li", + "lica", + "lice", + "lisica", + "listo", + "literatura", + "lobi", + "lodka", + "logichen", + "logika", + "lokalno", + "luk", + "luna", + "lyato", + "lyubimec", + "mach", + "mafiya", + "magazin", + "magistrala", + "maika", + "makaron", + "malina", + "malko", + "mama", + "mantiya", + "mantiyata", + "masa", + "masazh", + "mashtab", + "mashtaben", + "masiven", + "maslo", + "masovo", + "matematika", + "matrak", + "maya", + "maymuna", + "me", + "mechta", + "med", + "medal", + "mejdu", + "men", + "menta", + "mesa", + "mesec", + "meso", + "mi", + "minavam", + "miniatyura", + "minuta", + "miris", + "mislq", + "mlyako", + "mlyaskam", + "mnogo", + "moda", + "moga", + "mogila", + "moje", + "mokra", + "molba", + "moliv", + "molq", + "momche", + "moment", + "momiche", + "moq", + "more", + "morfema", + "morkov", + "most", + "mqsto", + "mu", + "muha", + "muzika", + "muzikalen", + "myau", + "na", + "nachin", + "nad", + "nadlez", + "nadpis", + "nahalen", + "nahodka", + "naistina", + "nakit", + "nali", + "nameci", + "namek", + "nameri", + "namerih", + "nametna", + "nametvam", + "namiram", + "naokolo", + "napravi", + "napravq", + "napred", + "nar", + "narcis", + "nared", + "naryazan", + "nas", + "nasekomi", + "nasekomo", + "nasip", + "nastavnik", + "natisk", + "navan", + "navik", + "navreme", + "ne", + "nebe", + "nebostargach", + "nego", + "negovi", + "negovite", + "nein", + "neka", + "nektar", + "neq", + "neshta", + "neshto", + "neyasen", + "neyn", + "nezhen", + "nezhna", + "ni", + "nie", + "nikoga", + "nikoi", + "nishto", + "nito", + "niva", + "no", + "nomer", + "nos", + "nosht", + "nosorog", + "nov", + "novator", + "novina", + "nozh", + "nqkoi", + "nqkolko", + "nqma", + "nqmam", + "nujda", + "nyam", + "obicham", + "obrabotka", + "obrabotvam", + "obrashtam", + "obraten", + "obratno", + "obsht", + "obshtestvo", + "obyad", + "ochakvam", + "ochi", + "oda", + "ogun", + "okean", + "oko", + "opa", + "opit", + "oriz", + "osem", + "oshte", + "ostrov", + "ostroven", + "ostrovi", + "ot", + "otgovor", + "otnovo", + "otrasal", + "otricanie", + "otrova", + "ovoshtna", + "pak", + "palezh", + "panica", + "pari", + "park", + "parvi", + "pasiven", + "pate", + "pateka", + "patentransport", + "patica", + "pavazh", + "pazar", + "pchela", + "pechka", + "peq", + "peron", + "pesen", + "pesh", + "pet", + "piesa", + "pile", + "pingvin", + "piramida", + "pisaci", + "pisak", + "pisan", + "pisatel", + "pismen", + "pismenost", + "pismo", + "pitka", + "placha", + "plaj", + "plan", + "planeta", + "planetarium", + "platno", + "pleshti", + "pleshtite", + "plet", + "pliskam", + "plitka", + "plod", + "plyaskam", + "po", + "pochasov", + "pod", + "podaden", + "podem", + "pogled", + "pokrivka", + "pole", + "polet", + "policay", + "policiya", + "politika", + "polyus", + "pomognaha", + "pomosht", + "populyaren", + "poradi", + "portal", + "posev", + "poshta", + "posle", + "posochvam", + "posoka", + "poveche", + "povecheto", + "pozor", + "prachka", + "prah", + "prase", + "prashen", + "prashtam", + "prat", + "pravo", + "pravq", + "prazen", + "praznik", + "pred", + "predi", + "predmet", + "predstavitel", + "predstoi", + "predstoya", + "predvid", + "premestvam", + "prenebregvam", + "prenebrezhenie", + "prevnya", + "prez", + "prezident", + "pri", + "pribiram", + "prikritie", + "prilagatelno", + "prilicham", + "prilichash", + "primer", + "printer", + "priqtel", + "priroda", + "prishivam", + "pristrasten", + "problem", + "profesionalist", + "profesiya", + "profesor", + "proizvoditel", + "prolet", + "promyana", + "proshepnesh", + "proshepvam", + "prost", + "prosto", + "prostor", + "prostota", + "provezhdam", + "prozorec", + "prust", + "ptica", + "ptici", + "puskam", + "put", + "q", + "rabota", + "radiator", + "radost", + "rak", + "rana", + "ranen", + "raneni", + "ranica", + "rano", + "ravno", + "ray", + "razbiram", + "razgovar", + "razhodka", + "razkaz", + "razmer", + "razstroystvo", + "raztvor", + "razvod", + "red", + "reka", + "relsa", + "repichka", + "republika", + "reshenie", + "restorant", + "rezba", + "rezha", + "riba", + "rod", + "roklya", + "royak", + "roza", + "rozova", + "rozovo", + "ruka", + "ryadak", + "ryadko", + "ryadkost", + "s", + "sa", + "sadarzhanie", + "sam", + "sama", + "samo", + "sastezanie", + "sastezavam", + "se", + "sebe", + "sedem", + "sedq", + "sega", + "selo", + "selski", + "semeystvo", + "seno", + "sgrada", + "shans", + "shepot", + "sheptya", + "shest", + "shirokolistno", + "shishe", + "shiya", + "shkaf", + "shpatula", + "shtastie", + "shte", + "shtora", + "si", + "siguren", + "sila", + "sin", + "skacham", + "skalp", + "skech", + "skelet", + "skoro", + "skorost", + "skorosten", + "skorpion", + "skupa", + "sladko", + "sled", + "slepec", + "sluchi", + "slunce", + "slyap", + "smakvam", + "smart", + "sme", + "snegovalezh", + "snezhen", + "snezhinka", + "snyag", + "sol", + "spane", + "spisak", + "spomen", + "sport", + "sravnenie", + "sravni", + "sreshta", + "stan", + "star", + "stara", + "starost", + "stava", + "stavam", + "staya", + "ste", + "stena", + "stene", + "stiga", + "stiker", + "sto", + "stol", + "stoy", + "strah", + "strana", + "strast", + "strelyam", + "struna", + "stud", + "sujelqvam", + "sum", + "sun", + "supermarket", + "surce", + "sus", + "sushto", + "sutrin", + "svarzvam", + "svesht", + "svetkavica", + "svoboda", + "svyat", + "syara", + "taka", + "tam", + "tanc", + "tate", + "tatko", + "tava", + "tavan", + "tayna", + "tazi", + "te", + "teb", + "tefter", + "tehen", + "telefon", + "televiziya", + "televizor", + "tendzhera", + "tendzheri", + "tetradka", + "tezi", + "ti", + "ticha", + "ticham", + "tigan", + "tih", + "tiho", + "tochno", + "togava", + "toi", + "tolkova", + "tor", + "tost", + "tova", + "tozi", + "tq", + "tqh", + "traktor", + "tramvay", + "tri", + "triagalnik", + "trinadeset", + "trinayset", + "trqbva", + "truden", + "tuk", + "tvard", + "tyalo", + "uchenichka", + "uchenik", + "uchilishte", + "uchitel", + "uedinenie", + "uho", + "ukrasa", + "ukrasyavam", + "um", + "uplaha", + "uplashen", + "uprazhnenie", + "ura", + "ured", + "uset", + "uspeh", + "uspeshen", + "ustroystvo", + "utre", + "uvazhenie", + "v", + "vagon", + "vampir", + "vana", + "vapros", + "var", + "varovik", + "vas", + "vaza", + "vazduh", + "vazhen", + "vcepenen", + "vchera", + "veche", + "vecher", + "vednaga", + "vesel", + "vest", + "vi", + "vidim", + "vidq", + "vie", + "vihar", + "vihrushka", + "vij", + "vilica", + "vinagi", + "vino", + "vinoven", + "vinovna", + "vinovnoto", + "visota", + "vkashti", + "vkus", + "vlak", + "vlast", + "vleze", + "voda", + "voden", + "vodenica", + "voleibol", + "vosak", + "voyna", + "vrag", + "vrah", + "vrata", + "vrazka", + "vreme", + "vse", + "vseki", + "vselena", + "vsichki", + "vsichko", + "vsushtnost", + "vuv", + "vyatar", + "vzimam", + "yabalka", + "yade", + "yadene", + "yagoda", + "yama", + "yami", + "yarak", + "yare", + "yarost", + "yasen", + "yastreb", + "yato", + "yayce", + "yod", + "yug", + "yuli", + "yuni", + "yurgan", + "yutiya", + "za", + "zadacha", + "zaedno", + "zaek", + "zakon", + "zala", + "zalez", + "zalezh", + "zalivam", + "zalyazvam", + "zapad", + "zapalvam", + "zaplaha", + "zar", + "zaradi", + "zashit", + "zashivam", + "zashtipvam", + "zashtitavam", + "zashto", + "zashtoto", + "zatova", + "zavartam", + "zavesa", + "zavod", + "zayavka", + "zdanie", + "zdrave", + "zdravei", + "zele", + "zelen", + "zhazhda", + "zhivotno", + "zhult", + "zima", + "zla", + "zlatna", + "zlato", + "zlo", + "zloba", + "zmey", + "zmiya", + "zna,", + "znachi", + "znae", + "znaesh", + "znaete", + "znanie", + "znaya", + "zub", + "zvuk", + "zvyar" + ] +} diff --git a/frontend/static/quotes/english.json b/frontend/static/quotes/english.json index f75c31058..d5c3455c6 100644 --- a/frontend/static/quotes/english.json +++ b/frontend/static/quotes/english.json @@ -39111,6 +39111,36 @@ "source": "George Orwell, 1984", "id": 7731, "length": 109 + }, + { + "text": "Commander! You may want to instruct your men to exercise restraint when using explosives... while certainly effective at killing aliens, they also destroy the artifacts we're hoping to recover from the bodies. Just something to consider.", + "source": "XCOM: Enemy Unknown", + "id": 7732, + "length": 237 + }, + { + "text": "This is unlike anything else we've previously identified. Based on its physical appearance, I would assume this alien doesn't rely on brute strength. I recommend the troops exercise extreme caution, Commander.", + "source": "XCOM: Enemy Unknown", + "id": 7733, + "length": 209 + }, + { + "text": "Ever since mankind first looked up at the stars, we have wondered what lies beyond. So very few have dared to look inward... The depths of the human mind hold more secrets than we can possibly imagine. How ironic that the means to defeat our enemy comes not through weapons or machines of war, but from within. And if we have succeeded... we will have gained a glimpse of what we are to become. We will have created something... extraordinary.", + "source": "XCOM: Enemy Unknown", + "id": 7734, + "length": 443 + }, + { + "text": "Incredible! That alien seems to have... taken control of that soldier somehow. All of the advances we've made so far... they would be useless against this type of power.", + "source": "XCOM: Enemy Unknown", + "id": 7735, + "length": 169 + }, + { + "text": "I have difficulty understanding how such an advanced species could show so little empathy for the lives of other sentient beings... It goes against everything we have ever imagined. The technology is there, but with it comes a callousness we would never have expected. What could have brought them to this...", + "source": "XCOM: Enemy Unknown", + "id": 7736, + "length": 308 } ] } diff --git a/frontend/vite-plugins/oxlint-checker.ts b/frontend/vite-plugins/oxlint-checker.ts new file mode 100644 index 000000000..5af753d29 --- /dev/null +++ b/frontend/vite-plugins/oxlint-checker.ts @@ -0,0 +1,312 @@ +import { Plugin, ViteDevServer, normalizePath } from "vite"; +import { spawn, execSync, ChildProcess } from "child_process"; +import { fileURLToPath } from "url"; + +export type OxlintCheckerOptions = { + /** Debounce delay in milliseconds before running lint after file changes. @default 125 */ + debounceDelay?: number; + /** Run type-aware checks (slower but more thorough). @default true */ + typeAware?: boolean; + /** Show browser overlay with lint status. @default true */ + overlay?: boolean; + /** File extensions to watch for changes. @default ['.ts', '.tsx', '.js', '.jsx'] */ + extensions?: string[]; +}; + +type LintResult = { + errorCount: number; + warningCount: number; + running: boolean; + hadIssues: boolean; + typeAware?: boolean; +}; + +const OXLINT_SUMMARY_REGEX = /Found (\d+) warnings? and (\d+) errors?/; + +export function oxlintChecker(options: OxlintCheckerOptions = {}): Plugin { + const { + debounceDelay = 125, + typeAware = true, + overlay = true, + extensions = [".ts", ".tsx", ".js", ".jsx"], + } = options; + + let currentProcess: ChildProcess | null = null; + let debounceTimer: NodeJS.Timeout | null = null; + let server: ViteDevServer | null = null; + let isProduction = false; + let currentRunId = 0; + let lastLintResult: LintResult = { + errorCount: 0, + warningCount: 0, + running: false, + hadIssues: false, + }; + + const killCurrentProcess = (): boolean => { + if ((currentProcess && !currentProcess.killed) || currentProcess !== null) { + currentProcess.kill(); + currentProcess = null; + return true; + } + return false; + }; + + const clearDebounceTimer = (): void => { + if (debounceTimer) { + clearTimeout(debounceTimer); + debounceTimer = null; + } + }; + + const parseLintOutput = ( + output: string, + ): Pick => { + const summaryMatch = output.match(OXLINT_SUMMARY_REGEX); + if (summaryMatch?.[1] !== undefined && summaryMatch?.[2] !== undefined) { + return { + warningCount: parseInt(summaryMatch[1], 10), + errorCount: parseInt(summaryMatch[2], 10), + }; + } + return { errorCount: 0, warningCount: 0 }; + }; + + const sendLintResult = (result: Partial): void => { + const previousHadIssues = lastLintResult.hadIssues; + + const payload: LintResult = { + errorCount: result.errorCount ?? lastLintResult.errorCount, + warningCount: result.warningCount ?? lastLintResult.warningCount, + running: result.running ?? false, + hadIssues: previousHadIssues, + typeAware: result.typeAware, + }; + + // Only update hadIssues when we have actual lint results (not just running status) + if (result.running === false) { + const currentHasIssues = + (result.errorCount ?? 0) > 0 || (result.warningCount ?? 0) > 0; + lastLintResult = { ...payload, hadIssues: currentHasIssues }; + } else { + // Keep hadIssues unchanged when just updating running status + lastLintResult = { ...payload, hadIssues: previousHadIssues }; + } + + if (server) { + server.ws.send("vite-plugin-oxlint", payload); + } + }; + + /** + * Runs an oxlint process with the given arguments and captures its combined output. + * + * This function is responsible for managing the lifecycle of the current lint process: + * - It spawns a new child process via `npx oxlint . ...args`. + * - It assigns the spawned process to the shared {@link currentProcess} variable so that + * other parts of the plugin can cancel or track the active lint run. + * - On process termination (either "error" or "close"), it clears {@link currentProcess} + * if it still refers to this child, avoiding interference with any newer process that + * may have started in the meantime. + * + * @param args Additional command-line arguments to pass to `oxlint`. + * @returns A promise that resolves with the process exit code (or `null` if + * the process exited due to a signal) and the full stdout/stderr output + * produced by the lint run. + */ + const runLintProcess = async ( + args: string[], + ): Promise<{ code: number | null; output: string }> => { + return new Promise((resolve) => { + const childProcess = spawn("npx", ["oxlint", ".", ...args], { + cwd: process.cwd(), + shell: true, + env: { ...process.env, FORCE_COLOR: "3" }, + }); + + currentProcess = childProcess; + let output = ""; + + childProcess.stdout?.on("data", (data: Buffer) => { + output += data.toString(); + }); + + childProcess.stderr?.on("data", (data: Buffer) => { + output += data.toString(); + }); + + childProcess.on("error", (error: Error) => { + output += `\nError: ${error.message}`; + if (currentProcess === childProcess) { + currentProcess = null; + } + resolve({ code: 1, output }); + }); + + childProcess.on("close", (code: number | null) => { + if (currentProcess === childProcess) { + currentProcess = null; + } + resolve({ code, output }); + }); + }); + }; + + const runOxlint = async (): Promise => { + const wasKilled = killCurrentProcess(); + const runId = ++currentRunId; + + console.log( + wasKilled + ? "\x1b[36mRunning oxlint...\x1b[0m \x1b[90m(killed previous process)\x1b[0m" + : "\x1b[36mRunning oxlint...\x1b[0m", + ); + + sendLintResult({ running: true }); + + // First pass: fast oxlint without type checking + const { code, output } = await runLintProcess([]); + + // Check if we were superseded by a newer run + if (runId !== currentRunId) { + return; + } + + if (output) { + console.log(output); + } + + // If first pass had errors, send them immediately (fast-fail) + if (code !== 0) { + const counts = parseLintOutput(output); + if (counts.errorCount > 0 || counts.warningCount > 0) { + sendLintResult({ ...counts, running: false }); + return; + } + } + + // First pass clean - run type-aware check if enabled + if (!typeAware) { + sendLintResult({ errorCount: 0, warningCount: 0, running: false }); + return; + } + + console.log("\x1b[36mRunning type-aware checks...\x1b[0m"); + sendLintResult({ running: true, typeAware: true }); + const typeResult = await runLintProcess(["--type-check", "--type-aware"]); + + // Check if we were superseded by a newer run + if (runId !== currentRunId) { + return; + } + + if (typeResult.output) { + console.log(typeResult.output); + } + + const counts = + typeResult.code !== 0 + ? parseLintOutput(typeResult.output) + : { errorCount: 0, warningCount: 0 }; + sendLintResult({ ...counts, running: false }); + }; + + const debouncedLint = (): void => { + clearDebounceTimer(); + sendLintResult({ running: true }); + debounceTimer = setTimeout(() => void runOxlint(), debounceDelay); + }; + + return { + name: "vite-plugin-oxlint-checker", + + config(_, { command }) { + isProduction = command === "build"; + }, + + configureServer(devServer: ViteDevServer) { + server = devServer; + + // Send current lint status to new clients on connection + devServer.ws.on("connection", () => { + devServer.ws.send("vite-plugin-oxlint", lastLintResult); + }); + + // Run initial lint + void runOxlint(); + + // Listen for file changes + devServer.watcher.on("change", (file: string) => { + // Only lint on relevant file changes + if (extensions.some((ext) => file.endsWith(ext))) { + debouncedLint(); + } + }); + }, + + transformIndexHtml() { + if (!overlay) { + return []; + } + + // Inject import to the overlay module (actual .ts file processed by Vite) + const overlayPath = normalizePath( + fileURLToPath(new URL("./oxlint-overlay.ts", import.meta.url)), + ); + return [ + { + tag: "script", + attrs: { + type: "module", + src: `/@fs${overlayPath}`, + }, + injectTo: "body-prepend", + }, + ]; + }, + + buildStart() { + // Only run during production builds, not dev server startup + if (!isProduction) { + return; + } + + // Run oxlint synchronously during build + console.log("\n\x1b[1mRunning oxlint...\x1b[0m"); + + try { + const output = execSync( + "npx oxlint . && npx oxlint . --type-aware --type-check", + { + cwd: process.cwd(), + encoding: "utf-8", + env: { ...process.env, FORCE_COLOR: "3" }, + }, + ); + + if (output) { + console.log(output); + } + console.log(` \x1b[32m✓ No linting issues found\x1b[0m\n`); + } catch (error) { + // execSync throws on non-zero exit code (linting errors found) + if (error instanceof Error && "stdout" in error) { + const execError = error as Error & { + stdout?: string; + stderr?: string; + }; + if (execError.stdout !== undefined) console.log(execError.stdout); + if (execError.stderr !== undefined) console.error(execError.stderr); + } + console.error("\n\x1b[31mBuild aborted due to linting errors\x1b[0m\n"); + process.exit(1); + } + }, + + closeBundle() { + // Cleanup on server close + killCurrentProcess(); + clearDebounceTimer(); + }, + }; +} diff --git a/frontend/vite-plugins/oxlint-overlay.ts b/frontend/vite-plugins/oxlint-overlay.ts new file mode 100644 index 000000000..a1d734031 --- /dev/null +++ b/frontend/vite-plugins/oxlint-overlay.ts @@ -0,0 +1,126 @@ +// Oxlint overlay client-side code +let overlay: HTMLDivElement | null = null; +let hideTimeout: ReturnType | null = null; + +function createOverlay(): HTMLDivElement { + if (overlay) return overlay; + + overlay = document.createElement("div"); + overlay.id = "oxlint-error-overlay"; + overlay.style.cssText = ` + position: fixed; + bottom: 20px; + left: 20px; + background: #323437; + color: #e4dec8ff; + padding: 12px 16px; + border-radius: 8px; + font-family: 'Roboto Mono', monospace; + font-size: 14px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); + z-index: 10000; + display: none; + align-items: center; + gap: 8px; + cursor: pointer; + transition: opacity 0.2s ease; + `; + + overlay.addEventListener("mouseenter", () => { + if (overlay) overlay.style.opacity = "0.5"; + }); + + overlay.addEventListener("mouseleave", () => { + if (overlay) overlay.style.opacity = "1"; + }); + + overlay.addEventListener("click", () => { + if (overlay) overlay.style.display = "none"; + }); + + document.body.appendChild(overlay); + return overlay; +} + +function updateOverlay(data: { + errorCount?: number; + warningCount?: number; + running?: boolean; + hadIssues?: boolean; + typeAware?: boolean; +}): void { + const overlayEl = createOverlay(); + + // Clear any pending hide timeout + if (hideTimeout !== null) { + clearTimeout(hideTimeout); + hideTimeout = null; + } + + // Show running icon if linting is running and there were issues before + if (data.running) { + if (data.hadIssues) { + const message = data.typeAware ? "checking type aware..." : "checking..."; + overlayEl.innerHTML = ` + + oxlint: ${message} + `; + overlayEl.style.display = "flex"; + overlayEl.style.color = "#e4dec8ff"; + } else { + overlayEl.style.display = "none"; + } + return; + } + + const { errorCount = 0, warningCount = 0, hadIssues = false } = data; + const total = errorCount + warningCount; + + if (total > 0) { + overlayEl.innerHTML = ` + 🚨 + oxlint: ${errorCount} error${errorCount !== 1 ? "s" : ""}, ${warningCount} warning${warningCount !== 1 ? "s" : ""} + `; + overlayEl.style.display = "flex"; + + if (errorCount > 0) { + overlayEl.style.color = "#e4dec8ff"; + } + } else { + // Only show success if the previous lint had issues + if (hadIssues) { + overlayEl.innerHTML = ` + + oxlint: ok + `; + overlayEl.style.display = "flex"; + overlayEl.style.color = "#e4dec8ff"; + + // Hide after 3 seconds + hideTimeout = setTimeout(() => { + overlayEl.style.display = "none"; + hideTimeout = null; + }, 3000); + } else { + // Two good lints in a row - don't show anything + overlayEl.style.display = "none"; + } + } +} + +// Initialize overlay on load +createOverlay(); + +if (import.meta.hot) { + import.meta.hot.on( + "vite-plugin-oxlint", + (data: { + errorCount?: number; + warningCount?: number; + running?: boolean; + hadIssues?: boolean; + }) => { + updateOverlay(data); + }, + ); +} diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index ed0eb5c81..0b81b5466 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -19,7 +19,7 @@ import { languageHashes } from "./vite-plugins/language-hashes"; import { minifyJson } from "./vite-plugins/minify-json"; import { versionFile } from "./vite-plugins/version-file"; import { jqueryInject } from "./vite-plugins/jquery-inject"; -import { checker } from "vite-plugin-checker"; +import { oxlintChecker } from "./vite-plugins/oxlint-checker"; import Inspect from "vite-plugin-inspect"; import { ViteMinifyPlugin } from "vite-plugin-minify"; import { VitePWA } from "vite-plugin-pwa"; @@ -81,13 +81,10 @@ function getPlugins({ const plugins: PluginOption[] = [ envConfig({ isDevelopment, clientVersion, env }), languageHashes({ skip: isDevelopment }), - checker({ - oxlint: { - lintCommand: "oxlint . --type-aware --type-check", - }, - overlay: { - initialIsOpen: false, - }, + oxlintChecker({ + debounceDelay: 125, + typeAware: true, + overlay: true, }), jqueryInject(), injectHTML(), diff --git a/package.json b/package.json index 03cc06169..d2337f81e 100644 --- a/package.json +++ b/package.json @@ -67,18 +67,18 @@ "@commitlint/cli": "17.7.1", "@commitlint/config-conventional": "19.2.2", "@monkeytype/release": "workspace:*", - "@vitest/coverage-v8": "4.0.8", + "@vitest/coverage-v8": "4.0.15", "conventional-changelog": "6.0.0", "husky": "8.0.1", "knip": "2.19.2", "lint-staged": "13.2.3", "only-allow": "1.2.1", - "oxfmt": "0.18.0", - "oxlint": "1.33.0", - "oxlint-tsgolint": "0.9.0", + "oxfmt": "0.19.0", + "oxlint": "1.34.0", + "oxlint-tsgolint": "0.9.2", "prettier": "3.7.1", "turbo": "2.5.6", - "vitest": "4.0.8" + "vitest": "4.0.15" }, "packageManager": "pnpm@9.6.0", "engines": { diff --git a/packages/contracts/package.json b/packages/contracts/package.json index d8ff23c92..949ca01fb 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -29,11 +29,11 @@ "@monkeytype/tsup-config": "workspace:*", "@monkeytype/typescript-config": "workspace:*", "madge": "8.0.0", - "oxlint": "1.33.0", - "oxlint-tsgolint": "0.9.0", + "oxlint": "1.34.0", + "oxlint-tsgolint": "0.9.2", "tsup": "8.4.0", "typescript": "5.9.3", - "vitest": "4.0.8" + "vitest": "4.0.15" }, "peerDependencies": { "@ts-rest/core": "3.52.1", diff --git a/packages/contracts/src/util/api.ts b/packages/contracts/src/util/api.ts index 275e4b8fc..3bd5c92d7 100644 --- a/packages/contracts/src/util/api.ts +++ b/packages/contracts/src/util/api.ts @@ -77,6 +77,8 @@ export const MonkeyValidationErrorSchema = MonkeyResponseSchema.extend({ export type MonkeyValidationError = z.infer; export const MonkeyClientError = MonkeyResponseSchema; +export type MonkeyClientErrorType = z.infer; + export const MonkeyServerError = MonkeyClientError.extend({ errorId: z.string(), uid: z.string().optional(), @@ -130,3 +132,17 @@ export const CommonResponses = { "Endpoint disabled or server is under maintenance", ), }; + +export type CommonResponsesType = + | { + status: 400 | 401 | 403 | 429 | 470 | 471 | 472 | 479; + body: MonkeyClientErrorType; + } + | { + status: 422; + body: MonkeyValidationError; + } + | { + status: 500 | 503; + body: MonkeyServerErrorType; + }; diff --git a/packages/funbox/package.json b/packages/funbox/package.json index 7cac67bf0..1a4c488e4 100644 --- a/packages/funbox/package.json +++ b/packages/funbox/package.json @@ -25,10 +25,10 @@ "@monkeytype/tsup-config": "workspace:*", "@monkeytype/typescript-config": "workspace:*", "madge": "8.0.0", - "oxlint": "1.33.0", - "oxlint-tsgolint": "0.9.0", + "oxlint": "1.34.0", + "oxlint-tsgolint": "0.9.2", "tsup": "8.4.0", "typescript": "5.9.3", - "vitest": "4.0.8" + "vitest": "4.0.15" } } diff --git a/packages/release/package.json b/packages/release/package.json index ab3c6b9bc..2ce3ce162 100644 --- a/packages/release/package.json +++ b/packages/release/package.json @@ -20,7 +20,7 @@ }, "devDependencies": { "nodemon": "3.1.4", - "oxlint": "1.33.0", - "oxlint-tsgolint": "0.9.0" + "oxlint": "1.34.0", + "oxlint-tsgolint": "0.9.2" } } diff --git a/packages/schemas/package.json b/packages/schemas/package.json index a4cb08fde..36efdfea8 100644 --- a/packages/schemas/package.json +++ b/packages/schemas/package.json @@ -26,11 +26,11 @@ "@monkeytype/tsup-config": "workspace:*", "@monkeytype/typescript-config": "workspace:*", "madge": "8.0.0", - "oxlint": "1.33.0", - "oxlint-tsgolint": "0.9.0", + "oxlint": "1.34.0", + "oxlint-tsgolint": "0.9.2", "tsup": "8.4.0", "typescript": "5.9.3", - "vitest": "4.0.8" + "vitest": "4.0.15" }, "peerDependencies": { "zod": "3.23.8" diff --git a/packages/schemas/src/languages.ts b/packages/schemas/src/languages.ts index d199e0e8a..7e26911a3 100644 --- a/packages/schemas/src/languages.ts +++ b/packages/schemas/src/languages.ts @@ -235,7 +235,9 @@ export const LanguageSchema = z.enum( "lithuanian_1k", "lithuanian_3k", "bulgarian", + "bulgarian_1k", "bulgarian_latin", + "bulgarian_latin_1k", "bangla", "bangla_letters", "bangla_10k", diff --git a/packages/tsup-config/package.json b/packages/tsup-config/package.json index 05f8d1ba6..f8d8e2fb9 100644 --- a/packages/tsup-config/package.json +++ b/packages/tsup-config/package.json @@ -17,8 +17,8 @@ }, "devDependencies": { "@monkeytype/typescript-config": "workspace:*", - "oxlint": "1.33.0", - "oxlint-tsgolint": "0.9.0", + "oxlint": "1.34.0", + "oxlint-tsgolint": "0.9.2", "typescript": "5.9.3" }, "peerDependencies": { diff --git a/packages/util/package.json b/packages/util/package.json index 4e66ba5c2..fa4d7dc7b 100644 --- a/packages/util/package.json +++ b/packages/util/package.json @@ -20,11 +20,11 @@ "@monkeytype/tsup-config": "workspace:*", "@monkeytype/typescript-config": "workspace:*", "madge": "8.0.0", - "oxlint": "1.33.0", - "oxlint-tsgolint": "0.9.0", + "oxlint": "1.34.0", + "oxlint-tsgolint": "0.9.2", "tsup": "8.4.0", "typescript": "5.9.3", - "vitest": "4.0.8", + "vitest": "4.0.15", "zod": "3.23.8" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bbd6b0752..29c2d2dca 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -18,8 +18,8 @@ importers: specifier: workspace:* version: link:packages/release '@vitest/coverage-v8': - specifier: 4.0.8 - version: 4.0.8(vitest@4.0.8(@types/node@20.5.1)(happy-dom@20.0.10)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1)) + specifier: 4.0.15 + version: 4.0.15(vitest@4.0.15(@types/node@20.5.1)(happy-dom@20.0.10)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1)) conventional-changelog: specifier: 6.0.0 version: 6.0.0(conventional-commits-filter@5.0.0) @@ -36,14 +36,14 @@ importers: specifier: 1.2.1 version: 1.2.1 oxfmt: - specifier: 0.18.0 - version: 0.18.0 + specifier: 0.19.0 + version: 0.19.0 oxlint: - specifier: 1.33.0 - version: 1.33.0(oxlint-tsgolint@0.9.0) + specifier: 1.34.0 + version: 1.34.0(oxlint-tsgolint@0.9.2) oxlint-tsgolint: - specifier: 0.9.0 - version: 0.9.0 + specifier: 0.9.2 + version: 0.9.2 prettier: specifier: 3.7.1 version: 3.7.1 @@ -51,8 +51,8 @@ importers: specifier: 2.5.6 version: 2.5.6 vitest: - specifier: 4.0.8 - version: 4.0.8(@types/node@20.5.1)(happy-dom@20.0.10)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1) + specifier: 4.0.15 + version: 4.0.15(@types/node@20.5.1)(happy-dom@20.0.10)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1) backend: dependencies: @@ -223,8 +223,8 @@ importers: specifier: 10.0.0 version: 10.0.0 '@vitest/coverage-v8': - specifier: 4.0.8 - version: 4.0.8(vitest@4.0.8(@types/node@24.9.1)(happy-dom@20.0.10)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1)) + specifier: 4.0.15 + version: 4.0.15(vitest@4.0.15(@opentelemetry/api@1.8.0)(@types/node@24.9.1)(happy-dom@20.0.10)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1)) concurrently: specifier: 8.2.2 version: 8.2.2 @@ -232,11 +232,11 @@ importers: specifier: 2.0.2 version: 2.0.2 oxlint: - specifier: 1.33.0 - version: 1.33.0(oxlint-tsgolint@0.9.0) + specifier: 1.34.0 + version: 1.34.0(oxlint-tsgolint@0.9.2) oxlint-tsgolint: - specifier: 0.9.0 - version: 0.9.0 + specifier: 0.9.2 + version: 0.9.2 readline-sync: specifier: 1.4.10 version: 1.4.10 @@ -244,8 +244,8 @@ importers: specifier: 7.1.4 version: 7.1.4 testcontainers: - specifier: 11.4.0 - version: 11.4.0 + specifier: 11.10.0 + version: 11.10.0 tsx: specifier: 4.16.2 version: 4.16.2 @@ -253,8 +253,8 @@ importers: specifier: 5.9.3 version: 5.9.3 vitest: - specifier: 4.0.8 - version: 4.0.8(@types/node@24.9.1)(happy-dom@20.0.10)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1) + specifier: 4.0.15 + version: 4.0.15(@opentelemetry/api@1.8.0)(@types/node@24.9.1)(happy-dom@20.0.10)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1) frontend: dependencies: @@ -407,8 +407,8 @@ importers: specifier: 5.0.2 version: 5.0.2 '@vitest/coverage-v8': - specifier: 4.0.8 - version: 4.0.8(vitest@4.0.8(@types/node@24.9.1)(happy-dom@20.0.10)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1)) + specifier: 4.0.15 + version: 4.0.15(vitest@4.0.15(@types/node@24.9.1)(happy-dom@20.0.10)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1)) autoprefixer: specifier: 10.4.20 version: 10.4.20(postcss@8.4.31) @@ -440,11 +440,11 @@ importers: specifier: 8.0.1 version: 8.0.1 oxlint: - specifier: 1.33.0 - version: 1.33.0(oxlint-tsgolint@0.9.0) + specifier: 1.34.0 + version: 1.34.0(oxlint-tsgolint@0.9.2) oxlint-tsgolint: - specifier: 0.9.0 - version: 0.9.0 + specifier: 0.9.2 + version: 0.9.2 postcss: specifier: 8.4.31 version: 8.4.31 @@ -469,9 +469,6 @@ importers: vite-bundle-visualizer: specifier: 1.2.1 version: 1.2.1(rollup@2.79.2) - vite-plugin-checker: - specifier: 0.11.0 - version: 0.11.0(eslint@9.39.1)(meow@13.2.0)(optionator@0.9.4)(oxlint@1.33.0(oxlint-tsgolint@0.9.0))(typescript@5.9.3)(vite@7.1.12(@types/node@24.9.1)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1)) vite-plugin-filter-replace: specifier: 0.1.14 version: 0.1.14 @@ -488,8 +485,8 @@ importers: specifier: 1.1.0 version: 1.1.0(vite@7.1.12(@types/node@24.9.1)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1))(workbox-build@7.1.1)(workbox-window@7.1.0) vitest: - specifier: 4.0.8 - version: 4.0.8(@types/node@24.9.1)(happy-dom@20.0.10)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1) + specifier: 4.0.15 + version: 4.0.15(@types/node@24.9.1)(happy-dom@20.0.10)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1) packages/contracts: dependencies: @@ -513,11 +510,11 @@ importers: specifier: 8.0.0 version: 8.0.0(typescript@5.9.3) oxlint: - specifier: 1.33.0 - version: 1.33.0(oxlint-tsgolint@0.9.0) + specifier: 1.34.0 + version: 1.34.0(oxlint-tsgolint@0.9.2) oxlint-tsgolint: - specifier: 0.9.0 - version: 0.9.0 + specifier: 0.9.2 + version: 0.9.2 tsup: specifier: 8.4.0 version: 8.4.0(postcss@8.5.6)(tsx@4.16.2)(typescript@5.9.3)(yaml@2.8.1) @@ -525,8 +522,8 @@ importers: specifier: 5.9.3 version: 5.9.3 vitest: - specifier: 4.0.8 - version: 4.0.8(@types/node@24.9.1)(happy-dom@20.0.10)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1) + specifier: 4.0.15 + version: 4.0.15(@opentelemetry/api@1.8.0)(@types/node@24.9.1)(happy-dom@20.0.10)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1) packages/funbox: dependencies: @@ -547,11 +544,11 @@ importers: specifier: 8.0.0 version: 8.0.0(typescript@5.9.3) oxlint: - specifier: 1.33.0 - version: 1.33.0(oxlint-tsgolint@0.9.0) + specifier: 1.34.0 + version: 1.34.0(oxlint-tsgolint@0.9.2) oxlint-tsgolint: - specifier: 0.9.0 - version: 0.9.0 + specifier: 0.9.2 + version: 0.9.2 tsup: specifier: 8.4.0 version: 8.4.0(postcss@8.5.6)(tsx@4.16.2)(typescript@5.9.3)(yaml@2.8.1) @@ -559,8 +556,8 @@ importers: specifier: 5.9.3 version: 5.9.3 vitest: - specifier: 4.0.8 - version: 4.0.8(@types/node@24.9.1)(happy-dom@20.0.10)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1) + specifier: 4.0.15 + version: 4.0.15(@opentelemetry/api@1.8.0)(@types/node@24.9.1)(happy-dom@20.0.10)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1) packages/oxlint-config: {} @@ -580,11 +577,11 @@ importers: specifier: 3.1.4 version: 3.1.4 oxlint: - specifier: 1.33.0 - version: 1.33.0(oxlint-tsgolint@0.9.0) + specifier: 1.34.0 + version: 1.34.0(oxlint-tsgolint@0.9.2) oxlint-tsgolint: - specifier: 0.9.0 - version: 0.9.0 + specifier: 0.9.2 + version: 0.9.2 packages/schemas: dependencies: @@ -602,11 +599,11 @@ importers: specifier: 8.0.0 version: 8.0.0(typescript@5.9.3) oxlint: - specifier: 1.33.0 - version: 1.33.0(oxlint-tsgolint@0.9.0) + specifier: 1.34.0 + version: 1.34.0(oxlint-tsgolint@0.9.2) oxlint-tsgolint: - specifier: 0.9.0 - version: 0.9.0 + specifier: 0.9.2 + version: 0.9.2 tsup: specifier: 8.4.0 version: 8.4.0(postcss@8.5.6)(tsx@4.16.2)(typescript@5.9.3)(yaml@2.8.1) @@ -614,8 +611,8 @@ importers: specifier: 5.9.3 version: 5.9.3 vitest: - specifier: 4.0.8 - version: 4.0.8(@types/node@24.9.1)(happy-dom@20.0.10)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1) + specifier: 4.0.15 + version: 4.0.15(@opentelemetry/api@1.8.0)(@types/node@24.9.1)(happy-dom@20.0.10)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1) packages/tsup-config: dependencies: @@ -627,11 +624,11 @@ importers: specifier: workspace:* version: link:../typescript-config oxlint: - specifier: 1.33.0 - version: 1.33.0(oxlint-tsgolint@0.9.0) + specifier: 1.34.0 + version: 1.34.0(oxlint-tsgolint@0.9.2) oxlint-tsgolint: - specifier: 0.9.0 - version: 0.9.0 + specifier: 0.9.2 + version: 0.9.2 typescript: specifier: 5.9.3 version: 5.9.3 @@ -650,11 +647,11 @@ importers: specifier: 8.0.0 version: 8.0.0(typescript@5.9.3) oxlint: - specifier: 1.33.0 - version: 1.33.0(oxlint-tsgolint@0.9.0) + specifier: 1.34.0 + version: 1.34.0(oxlint-tsgolint@0.9.2) oxlint-tsgolint: - specifier: 0.9.0 - version: 0.9.0 + specifier: 0.9.2 + version: 0.9.2 tsup: specifier: 8.4.0 version: 8.4.0(postcss@8.5.6)(tsx@4.16.2)(typescript@5.9.3)(yaml@2.8.1) @@ -662,8 +659,8 @@ importers: specifier: 5.9.3 version: 5.9.3 vitest: - specifier: 4.0.8 - version: 4.0.8(@types/node@24.9.1)(happy-dom@20.0.10)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1) + specifier: 4.0.15 + version: 4.0.15(@opentelemetry/api@1.8.0)(@types/node@24.9.1)(happy-dom@20.0.10)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1) zod: specifier: 3.23.8 version: 3.23.8 @@ -2567,113 +2564,113 @@ packages: resolution: {integrity: sha512-aKcOkyrorBGlajjRdVoJWHTxfxO1vCNHLJVlSDaRHDIdjU+pX8IYQPvPDkYiujKLbRnWU+1TBwEt0QRgSm4SGA==} engines: {node: '>=14'} - '@oxfmt/darwin-arm64@0.18.0': - resolution: {integrity: sha512-Dp3mOFUtLtG/0eDclI3rx2be5FEnUxh2T6zlq7Zx5Q1c+DS9LJF8H97nQx+O/OU+o0e0J90kN8Msf9v3j7Hhsg==} + '@oxfmt/darwin-arm64@0.19.0': + resolution: {integrity: sha512-FfNpn3ximwbBZCaS8WL2vAXcFuQFQgvv/brO6D1WdmL4pnFOgfBIpFfkeFnKfdmdpxtg9O0wF8NTcdw5Iyl2Bg==} cpu: [arm64] os: [darwin] - '@oxfmt/darwin-x64@0.18.0': - resolution: {integrity: sha512-Oe3KS4tdfLq+f1Ebje7D9BF7/0xmBYSMN6dWatSko1xfvu8oCZzKcNCcbwz0a8rBnoH/BnIiGFUEbgJDnA/JLQ==} + '@oxfmt/darwin-x64@0.19.0': + resolution: {integrity: sha512-31FWUHAgTdTzOslz0zoA60UDEdeZZpeM6wTzCdffetwLbHkK8ZuCuzd+DauHZPNlSU8G72iw4lF7Zve9pSkK9w==} cpu: [x64] os: [darwin] - '@oxfmt/linux-arm64-gnu@0.18.0': - resolution: {integrity: sha512-zW9z8LrAgL9bswgsaFOnQmoImCbq9YQST8B15FAoTeZYr9sb/VfdD6/PQEBLCptYlFmoTvmDNHjppi9g61NwHQ==} + '@oxfmt/linux-arm64-gnu@0.19.0': + resolution: {integrity: sha512-BkU9h39xKj/8uko82uFDJUharM8VxZO+uXKglpBnEC8XxWRzXocVCX4wpLT/Tfk4NyBy6fRM9DeISdZvQKZCjA==} cpu: [arm64] os: [linux] - '@oxfmt/linux-arm64-musl@0.18.0': - resolution: {integrity: sha512-mFnpcasZZv1s/+6YDPradhIgR2G7GWr+mnzgJMIKQOJAtX3b0vzG+FE4f7hDIZPEUhC0hvZSd8f31FjAtrX0aQ==} + '@oxfmt/linux-arm64-musl@0.19.0': + resolution: {integrity: sha512-wWYk6Z/3iC+0zZAUkVCcEHui/IsUqsl+GEm9o6H7oARPLisXajbwCQcmqYslUD7eK6OXdYoWriBkEvSX/5dU4A==} cpu: [arm64] os: [linux] - '@oxfmt/linux-x64-gnu@0.18.0': - resolution: {integrity: sha512-G1AXQNHVkPgVgRrAvDYdNi0x3gkvVyEmpL6WLMwoSjMnwYWQLt5/jrndHbS2Db8/CcmMo6VPP8lUQGvM4TRVaQ==} + '@oxfmt/linux-x64-gnu@0.19.0': + resolution: {integrity: sha512-EB/b3or437E3uDie8QxeU3eA502JcmR1koyIBcH9rFidY0cMik58xvw54tXCY3WpMRxEXf37aHZzUSDP3qJnZg==} cpu: [x64] os: [linux] - '@oxfmt/linux-x64-musl@0.18.0': - resolution: {integrity: sha512-M/KEIMHDr5lcK9gDxgIGr9xVwX7Pfl3PZs+6e8SySuckMgoJMkee6uWxhMdxws3dfU94saAkVmNQzM2+l3hvtA==} + '@oxfmt/linux-x64-musl@0.19.0': + resolution: {integrity: sha512-htMB45orYoa1oFSjSmoGgcBDsD47/0joDfqpa8TrTDI5qsW5kAedpFR5wSce8Is9oj7SJ07omhOj96P/QiekWA==} cpu: [x64] os: [linux] - '@oxfmt/win32-arm64@0.18.0': - resolution: {integrity: sha512-LunuNBTLOgfkYFjiHOeNHD/Ii5paO/GVyNPSEYBwEPULbdeMGnjHqxzQHLGU8QIQjwu55mp4uuBP0DAXTli2TQ==} + '@oxfmt/win32-arm64@0.19.0': + resolution: {integrity: sha512-x7+3Eh/VWdXEda+BUmAKYlhGrRJVera7RfWw47Yx8PJUGtNqBfeYGDbf0W59ceK8Z3bY3OinrmOO3d1jOuXzMQ==} cpu: [arm64] os: [win32] - '@oxfmt/win32-x64@0.18.0': - resolution: {integrity: sha512-0kJJwn4FPeGJ4uV6ofOCiY+oBKWg+RWpRjYc9bywTjDbxWR6JAqtuXkiARCJej8UD/rJeXnHYTF78sFALkwZyA==} + '@oxfmt/win32-x64@0.19.0': + resolution: {integrity: sha512-X+FKXBg2jx4CxF5SJs3xpx1msMw5JfxaGD5qBZYqlHGdryQsy6zUY+bQwDDcuy3Ic/WNGD8ZNEuggeYNE7jx/Q==} cpu: [x64] os: [win32] - '@oxlint-tsgolint/darwin-arm64@0.9.0': - resolution: {integrity: sha512-dztZZwzSzX58pgwQoYex5svEdISDNpEM0A0cB0ISeaD3mFsro7h2idMK6q373hxnhNw4Rs1eu/4dwPpKEz6ouw==} + '@oxlint-tsgolint/darwin-arm64@0.9.2': + resolution: {integrity: sha512-AujIYejzo2KgSWEn65FxkTwJ7Xkq106wUjyKYpljpEsrldO3QkW1yjj6osisoIjM/NdV0VO21YWjIGFIKd0/Ug==} cpu: [arm64] os: [darwin] - '@oxlint-tsgolint/darwin-x64@0.9.0': - resolution: {integrity: sha512-GK/TOqKTqP41/yHyCwO4tJ0jF4vAwlsTXhgGXMD6GgTXv4t8kPcX4EFHZOfgMf+E84vE4MsvLFNI+NXfVIqWzQ==} + '@oxlint-tsgolint/darwin-x64@0.9.2': + resolution: {integrity: sha512-rNLCdo5DjSBcMjRwND4eOaboPD30Ssp0sfqS3KpHvo/0acQmQVCJwOaDUO7s2a77YPDJ6jSaGDWYo8HLnEXALg==} cpu: [x64] os: [darwin] - '@oxlint-tsgolint/linux-arm64@0.9.0': - resolution: {integrity: sha512-Rc+ea/v2Y+AmZrCOMWMV2NxQ7h967ivELqn7+gCIw53dCnvwp0JsGfL4e1tNkLddXaCfrC11zW61Ayie224GNA==} + '@oxlint-tsgolint/linux-arm64@0.9.2': + resolution: {integrity: sha512-xVL66moJftyrvmFSwstWDQ1bqVjn9HCSz+QJLLInf3/b51xZl5QbbGEPX4bf4SKut+7v5ZEM527DbVGg1yRmKQ==} cpu: [arm64] os: [linux] - '@oxlint-tsgolint/linux-x64@0.9.0': - resolution: {integrity: sha512-i3QRMXHCIGxGQTvFyE8LTy96SBvToZpkHPirBs6dvtKfDFHeKMX2WSc44rUdoU/LhjF5qYyU0lTITzj4VFEY/w==} + '@oxlint-tsgolint/linux-x64@0.9.2': + resolution: {integrity: sha512-sVH2DADkdUmFZhggaMM/r8NOdn2Ud1Az/nJoGbAiV82Lipw3Kp6y6yV2w+NWfmETHZZ+0gwlWTZod7NSmQuR5w==} cpu: [x64] os: [linux] - '@oxlint-tsgolint/win32-arm64@0.9.0': - resolution: {integrity: sha512-D6jeKoCfNAwBXJyxab4gZk8Rw9BjzGgQPd7DdLRLjDRY6DkdK4PVUwNzbboZk2SAxluO/UkSUXpoEoTJdl+cIQ==} + '@oxlint-tsgolint/win32-arm64@0.9.2': + resolution: {integrity: sha512-0nvXzcY+W84Vo4pq5qh+0k3BsSb7ImzB9VaX5jIbjTNvx3EYS/KZzo0smhZ+drs43V4oNqJjgt2jnkeWF9Yj/g==} cpu: [arm64] os: [win32] - '@oxlint-tsgolint/win32-x64@0.9.0': - resolution: {integrity: sha512-v7yGASaDJ2GXrvlDLwhrASRyM8zpOxU8oi3BLqE1mqbKSNbWNeTAaBfRNLyCUztiV1eFvx54hnF0AElPfglVYQ==} + '@oxlint-tsgolint/win32-x64@0.9.2': + resolution: {integrity: sha512-OGx2CIyWjpOhFOL7tvVjLrKlkjJSOVZBe5S/jmBnxY+vRjFrXQr0zNUXy+S+ggIv6rsMlz3B+ixSriyUIKwDeQ==} cpu: [x64] os: [win32] - '@oxlint/darwin-arm64@1.33.0': - resolution: {integrity: sha512-PmEQDLHAxiAdyttQ1ZWXd+5VpHLbHf3FTMJL9bg5TZamDnhNiW/v0Pamv3MTAdymnoDI3H8IVLAN/SAseV/adw==} + '@oxlint/darwin-arm64@1.34.0': + resolution: {integrity: sha512-euz3Dtp5/UE9axFkQnllOWp3gOwoqaxfZPUUwiW8HBelqhI9PRMVIfQ/akmwl+G5XixQZIgXkXQ5SJxnb1+Qww==} cpu: [arm64] os: [darwin] - '@oxlint/darwin-x64@1.33.0': - resolution: {integrity: sha512-2R9aH3kR0X2M30z5agGikv3tfNTi8/uLhU5/tYktu33VGUXpbf0OLZSlD25UEuwOKAlf3RVtzV5oDyjoq93JuQ==} + '@oxlint/darwin-x64@1.34.0': + resolution: {integrity: sha512-XpmNviE5KOnHkhmQPwJJIBs+mJkr0qItTZBN4dz+O3p9gWN+gCqi3CBP71RiVahZw4qAEQSgY4wro+z0kx+erg==} cpu: [x64] os: [darwin] - '@oxlint/linux-arm64-gnu@1.33.0': - resolution: {integrity: sha512-yb/k8GaMDgnX2LyO6km33kKItZ/n573SlbiHBBFU2HmeU7tzEHL5jHkHQXXcysUkapmqHd7UsDhOZDqPmXaQRg==} + '@oxlint/linux-arm64-gnu@1.34.0': + resolution: {integrity: sha512-aCPdoEUGsJGF9y88vDYoaugG4IEGwSBa+usyuAvEVl3vTfuTmE0RDQEC1Z+WnJ3J/cIEpbgKOzS12VwbzFicjg==} cpu: [arm64] os: [linux] - '@oxlint/linux-arm64-musl@1.33.0': - resolution: {integrity: sha512-03pt9IO1C4ZfVOW6SQiOK26mzklAhLM3Kc79OXpX1kgZRlxk+rvFoMhlgCOzn7tEdrEgbePkBoxNnwDnJDFqJQ==} + '@oxlint/linux-arm64-musl@1.34.0': + resolution: {integrity: sha512-cMo72LQBFmdnVLRKLAHD94ZUBq5Z+aA9Y+RKzkjhCmJuef5ZAfKC24TJD/6c5LxGYzkwwmyySoQAHq5B69i3PQ==} cpu: [arm64] os: [linux] - '@oxlint/linux-x64-gnu@1.33.0': - resolution: {integrity: sha512-Z7ImLWM50FoVXzYvyxUQ+QwBkBfRyK4YdLEGonyAGMp7iT3DksonDaTK9ODnJ1qHyAyAZCvuqXD7AEDsDvzDbA==} + '@oxlint/linux-x64-gnu@1.34.0': + resolution: {integrity: sha512-+9xFhhkqgNIysEh+uHvcba8v4UtL1YzxuyDS2wTLdWrkGvIllCx5WjJItt3K/AhwatciksgNEXSo2Hh4fcQRog==} cpu: [x64] os: [linux] - '@oxlint/linux-x64-musl@1.33.0': - resolution: {integrity: sha512-idb55Uzu5kkqqpMiVUfI9nP7zOqPZinQKsIRQAIU40wILcf/ijvhNZKIu3ucDMmye0n6IWOaSnxIRL5W2fNoUQ==} + '@oxlint/linux-x64-musl@1.34.0': + resolution: {integrity: sha512-qa7TL2DfEDdMeSP5UiU5JMs6D2PW7ZJAQ0WZYTgqDV8BlZ6nMkIYVBVIk3QPxIfkyxvfJVbG1RB3PkSWDcfwpA==} cpu: [x64] os: [linux] - '@oxlint/win32-arm64@1.33.0': - resolution: {integrity: sha512-wKKFt7cubfrLelNzdmDsNSmtBrlSUe1fWus587+uSxDZdpFbQ7liU0gsUlCbcHvym0H1Tc2O3K3cnLrgQORLPQ==} + '@oxlint/win32-arm64@1.34.0': + resolution: {integrity: sha512-mSJumUveg1S3DiOgvsrVNAGuvenBbbC/zsfT1qhltT+GLhJ7RPBK2I/jz0fTdE+I7M9/as8yc0XJ/eY23y2amA==} cpu: [arm64] os: [win32] - '@oxlint/win32-x64@1.33.0': - resolution: {integrity: sha512-ReyR8rNHjKNnO7dxGny9RCPELRAdhm3y780FNBcA07E1wvxSCkB+Mn5db0Pa5bRmxrsU/MTZ/aaBFa+ERXDdXw==} + '@oxlint/win32-x64@1.34.0': + resolution: {integrity: sha512-izsDDt5WY4FSISCkPRLUYQD1aRaaEJkPLtEZe3DlioSUdUVAdvVbE+BGllFqR16DWfTTwO/6K4jDeooxQzTMjw==} cpu: [x64] os: [win32] @@ -3230,8 +3227,8 @@ packages: '@types/docker-modem@3.0.6': resolution: {integrity: sha512-yKpAGEuKRSS8wwx0joknWxsmLha78wNMe9R2S3UNsVOkZded8UqOrV8KoeDXoXsjndxwyF3eIhyClGbO1SEhEg==} - '@types/dockerode@3.3.42': - resolution: {integrity: sha512-U1jqHMShibMEWHdxYhj3rCMNCiLx5f35i4e3CEUuW+JSSszc/tVqc6WCAPdhwBymG5R/vgbcceagK0St7Cq6Eg==} + '@types/dockerode@3.3.47': + resolution: {integrity: sha512-ShM1mz7rCjdssXt7Xz0u1/R2BJC7piWa3SJpUBiVjCf2A3XNn4cP6pUVaD8bLanpPVVn4IKzJuw3dOvkJ8IbYw==} '@types/estree@0.0.39': resolution: {integrity: sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==} @@ -3418,20 +3415,20 @@ packages: resolution: {integrity: sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==} engines: {node: ^18.18.0 || >=20.0.0} - '@vitest/coverage-v8@4.0.8': - resolution: {integrity: sha512-wQgmtW6FtPNn4lWUXi8ZSYLpOIb92j3QCujxX3sQ81NTfQ/ORnE0HtK7Kqf2+7J9jeveMGyGyc4NWc5qy3rC4A==} + '@vitest/coverage-v8@4.0.15': + resolution: {integrity: sha512-FUJ+1RkpTFW7rQITdgTi93qOCWJobWhBirEPCeXh2SW2wsTlFxy51apDz5gzG+ZEYt/THvWeNmhdAoS9DTwpCw==} peerDependencies: - '@vitest/browser': 4.0.8 - vitest: 4.0.8 + '@vitest/browser': 4.0.15 + vitest: 4.0.15 peerDependenciesMeta: '@vitest/browser': optional: true - '@vitest/expect@4.0.8': - resolution: {integrity: sha512-Rv0eabdP/xjAHQGr8cjBm+NnLHNoL268lMDK85w2aAGLFoVKLd8QGnVon5lLtkXQCoYaNL0wg04EGnyKkkKhPA==} + '@vitest/expect@4.0.15': + resolution: {integrity: sha512-Gfyva9/GxPAWXIWjyGDli9O+waHDC0Q0jaLdFP1qPAUUfo1FEXPXUfUkp3eZA0sSq340vPycSyOlYUeM15Ft1w==} - '@vitest/mocker@4.0.8': - resolution: {integrity: sha512-9FRM3MZCedXH3+pIh+ME5Up2NBBHDq0wqwhOKkN4VnvCiKbVxddqH9mSGPZeawjd12pCOGnl+lo/ZGHt0/dQSg==} + '@vitest/mocker@4.0.15': + resolution: {integrity: sha512-CZ28GLfOEIFkvCFngN8Sfx5h+Se0zN+h4B7yOsPVCcgtiO7t5jt9xQh2E1UkFep+eb9fjyMfuC5gBypwb07fvQ==} peerDependencies: msw: ^2.4.9 vite: ^6.0.0 || ^7.0.0-0 @@ -3441,20 +3438,20 @@ packages: vite: optional: true - '@vitest/pretty-format@4.0.8': - resolution: {integrity: sha512-qRrjdRkINi9DaZHAimV+8ia9Gq6LeGz2CgIEmMLz3sBDYV53EsnLZbJMR1q84z1HZCMsf7s0orDgZn7ScXsZKg==} + '@vitest/pretty-format@4.0.15': + resolution: {integrity: sha512-SWdqR8vEv83WtZcrfLNqlqeQXlQLh2iilO1Wk1gv4eiHKjEzvgHb2OVc3mIPyhZE6F+CtfYjNlDJwP5MN6Km7A==} - '@vitest/runner@4.0.8': - resolution: {integrity: sha512-mdY8Sf1gsM8hKJUQfiPT3pn1n8RF4QBcJYFslgWh41JTfrK1cbqY8whpGCFzBl45LN028g0njLCYm0d7XxSaQQ==} + '@vitest/runner@4.0.15': + resolution: {integrity: sha512-+A+yMY8dGixUhHmNdPUxOh0la6uVzun86vAbuMT3hIDxMrAOmn5ILBHm8ajrqHE0t8R9T1dGnde1A5DTnmi3qw==} - '@vitest/snapshot@4.0.8': - resolution: {integrity: sha512-Nar9OTU03KGiubrIOFhcfHg8FYaRaNT+bh5VUlNz8stFhCZPNrJvmZkhsr1jtaYvuefYFwK2Hwrq026u4uPWCw==} + '@vitest/snapshot@4.0.15': + resolution: {integrity: sha512-A7Ob8EdFZJIBjLjeO0DZF4lqR6U7Ydi5/5LIZ0xcI+23lYlsYJAfGn8PrIWTYdZQRNnSRlzhg0zyGu37mVdy5g==} - '@vitest/spy@4.0.8': - resolution: {integrity: sha512-nvGVqUunyCgZH7kmo+Ord4WgZ7lN0sOULYXUOYuHr55dvg9YvMz3izfB189Pgp28w0vWFbEEfNc/c3VTrqrXeA==} + '@vitest/spy@4.0.15': + resolution: {integrity: sha512-+EIjOJmnY6mIfdXtE/bnozKEvTC4Uczg19yeZ2vtCz5Yyb0QQ31QWVQ8hswJ3Ysx/K2EqaNsVanjr//2+P3FHw==} - '@vitest/utils@4.0.8': - resolution: {integrity: sha512-pdk2phO5NDvEFfUTxcTP8RFYjVj/kfLSPIN5ebP2Mu9kcIMeAQTbknqcFEyBcC4z2pJlJI9aS5UQjcYfhmKAow==} + '@vitest/utils@4.0.15': + resolution: {integrity: sha512-HXjPW2w5dxhTD0dLwtYHDnelK3j8sR8cWIaLxr22evTyY6q8pRCjZSmhRWVjBaOVXChQd6AwMzi9pucorXCPZA==} '@vue/compiler-core@3.4.37': resolution: {integrity: sha512-ZDDT/KiLKuCRXyzWecNzC5vTcubGz4LECAtfGPENpo0nrmqJHwuWtRLxk/Sb9RAKtR9iFflFycbkjkY+W/PZUQ==} @@ -3771,9 +3768,6 @@ packages: balloon-css@1.2.0: resolution: {integrity: sha512-urXwkHgwp6GsXVF+it01485Z2Cj4pnW02ICnM0TemOlkKmCNnDLmyy+ZZiRXBpwldUXO+aRNr7Hdia4CBvXJ5A==} - bare-events@2.4.2: - resolution: {integrity: sha512-qMKFd2qG/36aA4GwvKq8MxnPgCQAmBWmSyLWsJcbn8v03wvIPQ/hG1Ms8bPzndZxMDoHpxez5VOS+gC9Yi24/Q==} - bare-events@2.6.0: resolution: {integrity: sha512-EKZ5BTXYExaNqi3I3f9RtEsaI/xBSGjE0XZCZilPzFAV/goswFHuPd9jEZlPIZ/iNZJwDSao9qRiScySz7MbQg==} @@ -3807,8 +3801,8 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - baseline-browser-mapping@2.9.5: - resolution: {integrity: sha512-D5vIoztZOq1XM54LUdttJVc96ggEsIfju2JBvht06pSzpckp3C7HReun67Bghzrtdsq9XdMGbSSB3v3GhMNmAA==} + baseline-browser-mapping@2.9.10: + resolution: {integrity: sha512-2VIKvDx8Z1a9rTB2eCkdPE5nSe28XnA+qivGnWHoB40hMMt/h1hSz0960Zqsn6ZyxWXUie0EBdElKv8may20AA==} hasBin: true basic-auth-connect@1.0.0: @@ -3888,8 +3882,13 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true - browserslist@4.28.1: - resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==} + browserslist@4.24.4: + resolution: {integrity: sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + browserslist@4.28.0: + resolution: {integrity: sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true @@ -3998,14 +3997,17 @@ packages: caniuse-lite@1.0.30001649: resolution: {integrity: sha512-fJegqZZ0ZX8HOWr6rcafGr72+xcgJKI9oWfDW5DrD7ExUtgZC7a7R7ZYmZqplh7XDocFdGeIFn7roAxhOeYrPQ==} - caniuse-lite@1.0.30001759: - resolution: {integrity: sha512-Pzfx9fOKoKvevQf8oCXoyNRQ5QyxJj+3O0Rqx2V5oxT61KGx8+n6hV/IUyJeifUci2clnmmKVpvtiqRzgiWjSw==} + caniuse-lite@1.0.30001715: + resolution: {integrity: sha512-7ptkFGMm2OAOgvZpwgA4yjQ5SQbrNVGdRjzH0pBdy1Fasvcr+KAeECmbCAECzTuDuoX0FCY8KzUxjf9+9kfZEw==} + + caniuse-lite@1.0.30001761: + resolution: {integrity: sha512-JF9ptu1vP2coz98+5051jZ4PwQgd2ni8A+gYSN7EA7dPKIMf0pDlSUxhdmVOaV3/fYK5uWBkgSXJaRLr4+3A6g==} canvas-confetti@1.5.1: resolution: {integrity: sha512-Ncz+oZJP6OvY7ti4E1slxVlyAV/3g7H7oQtcCDXgwGgARxPnwYY9PW5Oe+I8uvspYNtuHviAdgA0LfcKFWJfpg==} - chai@6.2.0: - resolution: {integrity: sha512-aUTnJc/JipRzJrNADXVvpVqi6CO0dn3nx4EVPxijri+fj3LUUDyZQOgVeW54Ob3Y1Xh9Iz8f+CgaCl8v0mn9bA==} + chai@6.2.1: + resolution: {integrity: sha512-p4Z49OGG5W/WBCPSS/dH3jQ73kD6tiMmUM+bckNK6Jr5JHMG3k9bg/BvKR8lKmtVBKmOiuVaV2ws8s9oSbwysg==} engines: {node: '>=18'} chalk@4.1.2: @@ -4016,10 +4018,6 @@ packages: resolution: {integrity: sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} - chalk@5.3.0: - resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==} - engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} - chalk@5.6.2: resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} @@ -4720,16 +4718,16 @@ packages: disposable-email-domains-js@1.16.0: resolution: {integrity: sha512-5BTVWEfUO1xeJ/jQdt+bIeOFYlgKuN5wGUtiMlkj0rqqpeO8LwiIH+7sKYvob87yMBjDKJWD8eqSSHFVM0g2pQ==} - docker-compose@1.2.0: - resolution: {integrity: sha512-wIU1eHk3Op7dFgELRdmOYlPYS4gP8HhH1ZmZa13QZF59y0fblzFDFmKPhyc05phCy2hze9OEvNZAsoljrs+72w==} + docker-compose@1.3.0: + resolution: {integrity: sha512-7Gevk/5eGD50+eMD+XDnFnOrruFkL0kSd7jEG4cjmqweDSUhB7i0g8is/nBdVpl+Bx338SqIB2GLKm32M+Vs6g==} engines: {node: '>= 6.0.0'} docker-modem@5.0.6: resolution: {integrity: sha512-ens7BiayssQz/uAxGzH8zGXCtiV24rRWXdjNha5V4zSOcxmAZsfGVm/PPFbwQdqEkDnhG+SyR9E3zSHUbOKXBQ==} engines: {node: '>= 8.0'} - dockerode@4.0.7: - resolution: {integrity: sha512-R+rgrSRTRdU5mH14PZTCPZtW/zw3HDWNTS/1ZAQpL/5Upe/ye5K9WQkIysu4wBoiMwKynsz0a8qWuGsHgEvSAA==} + dockerode@4.0.9: + resolution: {integrity: sha512-iND4mcOWhPaCNh54WmK/KoSb35AFqPAUWFMffTQcp52uQt36b5uNwEJTSXntJZBbeGad72Crbi/hvDIv6us/6Q==} engines: {node: '>= 8.0'} dom-serializer@1.4.1: @@ -4806,6 +4804,9 @@ packages: engines: {node: '>=0.10.0'} hasBin: true + electron-to-chromium@1.5.144: + resolution: {integrity: sha512-eJIaMRKeAzxfBSxtjYnoIAw/tdD6VIH6tHBZepZnAbE3Gyqqs5mGN87DvcldPUbVkIljTK8pY0CMcUljP64lfQ==} + electron-to-chromium@1.5.267: resolution: {integrity: sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==} @@ -7045,6 +7046,9 @@ packages: node-releases@2.0.18: resolution: {integrity: sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==} + node-releases@2.0.19: + resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} + node-releases@2.0.27: resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} @@ -7105,10 +7109,6 @@ packages: resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - npm-run-path@6.0.0: - resolution: {integrity: sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==} - engines: {node: '>=18'} - npmlog@5.0.1: resolution: {integrity: sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==} deprecated: This package is no longer supported. @@ -7159,6 +7159,9 @@ packages: resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} engines: {node: '>= 0.4'} + obug@2.1.1: + resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + ohash@2.0.11: resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} @@ -7235,21 +7238,21 @@ packages: resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} engines: {node: '>= 0.4'} - oxfmt@0.18.0: - resolution: {integrity: sha512-XqVS+LQi7ARsFlIftB69+jGN2qQaJNBjQU2b9ZQYt+iaUV9digh+dBIsU0isQIZhaQ1ihxGFfsTYQXE/Oak8Qw==} + oxfmt@0.19.0: + resolution: {integrity: sha512-tPTa3j4kXdJBzBRlK9wR0/Lnd4J21rzg29cRr/VVqqfvdhZs6M+Q6TkL+rxI/IQpq8ZY8L3c+KZvga/RgeuMsg==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true - oxlint-tsgolint@0.9.0: - resolution: {integrity: sha512-JzZ3w7DAgf3RE3O7a4uNLtLUy8Q46r4SN4NOsfRv7lwuhLNhaXoVwOpwdPvAdfQeIJ+HSMgNkjP44vdSdC7O9A==} + oxlint-tsgolint@0.9.2: + resolution: {integrity: sha512-UKLudgnuEPyAzpLk5FeZyCdjRaHQE6cSqmlTBwmEp2lGJ1wN59qGc4gfjAXVSCrKrd30jXDGf5/zQ3CTa6BNNQ==} hasBin: true - oxlint@1.33.0: - resolution: {integrity: sha512-4WCL0K8jiOshwJ8WrVk35VAuVaZHC0iX6asjKsrENOrynkAAGcTLLx0Urf0eXZ1Tq7r+qAe3Z9EyHMFPzVyUkg==} + oxlint@1.34.0: + resolution: {integrity: sha512-Ni0N8wAiKlgaYkI/Yz4VrutfVIZgd2shDtS+loQxyBTwO8YUAnk3+g7OQ1cyI/aatHiFwvFNfV/uvZyHUtyPpA==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: - oxlint-tsgolint: '>=0.9.0' + oxlint-tsgolint: '>=0.9.2' peerDependenciesMeta: oxlint-tsgolint: optional: true @@ -7684,9 +7687,6 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - queue-tick@1.0.1: - resolution: {integrity: sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==} - quick-format-unescaped@4.0.4: resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} @@ -8001,9 +8001,6 @@ packages: run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} - rxjs@7.8.1: - resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} - rxjs@7.8.2: resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} @@ -8410,9 +8407,6 @@ packages: stream-to-array@2.3.0: resolution: {integrity: sha512-UsZtOYEn4tWU2RGLOXr/o/xjRBftZRlG3dEWoaHr8j4GuypJ3isitGbVyjQKAuMu+xbiop8q224TjiZWc4XTZA==} - streamx@2.18.0: - resolution: {integrity: sha512-LLUC1TWdjVdn1weXGcSxyTR3T4+acB6tVGXT95y0nGbca4t4o/ng1wKAGTljm9VicuCVLvRlqFYXYy5GwgM7sQ==} - streamx@2.22.1: resolution: {integrity: sha512-znKXEBxfatz2GBNK02kRnCXjV+AA4kjZIUxeWSr3UGirZMJfTE9uiwKHobnbgxWyL/JWro8tTq+vOqAK1/qbSA==} @@ -8586,11 +8580,11 @@ packages: resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} engines: {node: '>=6'} - tar-fs@2.1.3: - resolution: {integrity: sha512-090nwYJDmlhwFwEW3QQl+vaNnxsO2yVsd45eTKRBzSzu+hlb1w2K9inVq5b0ngXuLVqQ4ApvsUHHnu/zQNkWAg==} + tar-fs@2.1.4: + resolution: {integrity: sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==} - tar-fs@3.1.0: - resolution: {integrity: sha512-5Mty5y/sOF1YWj1J6GiBodjlDc05CUR8PKXrsnFAiSG0xA+GHeWLovaZPYUDXkH/1iKRf2+M5+OrRgzC7O9b7w==} + tar-fs@3.1.1: + resolution: {integrity: sha512-LZA0oaPOc2fVo82Txf3gw+AkEd38szODlptMYejQUhndHMLQ9M059uXR+AfS7DNo0NpINvSqDsvyaCrBVkptWg==} tar-stream@2.2.0: resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} @@ -8631,8 +8625,8 @@ packages: engines: {node: '>=10'} hasBin: true - testcontainers@11.4.0: - resolution: {integrity: sha512-eX5nc/Fi5I0LHqwxw6BuUvWNfdl+M2sKX6fX/47RP89Xs5nU6smd0iD7dpFogxy8/wACjlucLoutJc7b5mtq7w==} + testcontainers@11.10.0: + resolution: {integrity: sha512-8hwK2EnrOZfrHPpDC7CPe03q7H8Vv8j3aXdcmFFyNV8dzpBzgZYmqyDtduJ8YQ5kbzj+A+jUXMQ6zI8B5U3z+g==} text-decoder@1.1.1: resolution: {integrity: sha512-8zll7REEv4GDD3x4/0pW+ppIxSNs7H1J10IKFZsuOMscumCdM2a+toDGLPA3T+1+fLBql4zbt5z83GEQGGV5VA==} @@ -8664,9 +8658,6 @@ packages: through@2.3.8: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} - tiny-invariant@1.3.3: - resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} - tiny-lru@8.0.2: resolution: {integrity: sha512-ApGvZ6vVvTNdsmt676grvCkUCGwzG9IqXma5Z07xJgiC5L7akUMof5U8G2JTI9Rz/ovtVhJBlY6mNhEvtjzOIg==} engines: {node: '>=6'} @@ -8677,6 +8668,10 @@ packages: tinyexec@0.3.2: resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + tinyexec@1.0.2: + resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==} + engines: {node: '>=18'} + tinyglobby@0.2.13: resolution: {integrity: sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==} engines: {node: '>=12.0.0'} @@ -8956,8 +8951,8 @@ packages: resolution: {integrity: sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==} engines: {node: '>=18.17'} - undici@7.12.0: - resolution: {integrity: sha512-GrKEsc3ughskmGA9jevVlIOPMiiAHJ4OFUtaAH+NhfTUSiZ1wMPIQqQvAJUrJspFXJt3EBWgpAeoHEDVT1IBug==} + undici@7.16.0: + resolution: {integrity: sha512-QEg3HPMll0o3t2ourKwOeUAZ159Kn9mx5pnzHRQO8+Wixmh88YdZRiIwat0iNzNNXn0yoEtXJqFpyW7eM8BV7g==} engines: {node: '>=20.18.1'} unescape-js@1.1.4: @@ -8987,10 +8982,6 @@ packages: resolution: {integrity: sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==} engines: {node: '>=18'} - unicorn-magic@0.3.0: - resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==} - engines: {node: '>=18'} - unique-filename@3.0.0: resolution: {integrity: sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} @@ -9048,8 +9039,14 @@ packages: peerDependencies: browserslist: '>= 4.21.0' - update-browserslist-db@1.2.2: - resolution: {integrity: sha512-E85pfNzMQ9jpKkA7+TJAi4TJN+tBCuWh5rUcS/sv6cFi+1q9LYDwDI5dpUL0u/73EElyQ8d3TEaeW4sPedBqYA==} + update-browserslist-db@1.1.3: + resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' @@ -9129,43 +9126,6 @@ packages: peerDependencies: vite: ^2.6.0 || ^3.0.0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0 - vite-plugin-checker@0.11.0: - resolution: {integrity: sha512-iUdO9Pl9UIBRPAragwi3as/BXXTtRu4G12L3CMrjx+WVTd9g/MsqNakreib9M/2YRVkhZYiTEwdH2j4Dm0w7lw==} - engines: {node: '>=16.11'} - peerDependencies: - '@biomejs/biome': '>=1.7' - eslint: '>=7' - meow: ^13.2.0 - optionator: ^0.9.4 - oxlint: '>=1' - stylelint: '>=16' - typescript: '*' - vite: '>=5.4.20' - vls: '*' - vti: '*' - vue-tsc: ~2.2.10 || ^3.0.0 - peerDependenciesMeta: - '@biomejs/biome': - optional: true - eslint: - optional: true - meow: - optional: true - optionator: - optional: true - oxlint: - optional: true - stylelint: - optional: true - typescript: - optional: true - vls: - optional: true - vti: - optional: true - vue-tsc: - optional: true - vite-plugin-filter-replace@0.1.14: resolution: {integrity: sha512-3onEAYgjJQTCJN+/f8+1vOUH3X/tRmXsfxmgH3QtpReGKi+xtP1jMpAEnYsNqw9mI2shZmxFm3LS+n52XczwYQ==} @@ -9239,24 +9199,24 @@ packages: yaml: optional: true - vitest@4.0.8: - resolution: {integrity: sha512-urzu3NCEV0Qa0Y2PwvBtRgmNtxhj5t5ULw7cuKhIHh3OrkKTLlut0lnBOv9qe5OvbkMH2g38G7KPDCTpIytBVg==} + vitest@4.0.15: + resolution: {integrity: sha512-n1RxDp8UJm6N0IbJLQo+yzLZ2sQCDyl1o0LeugbPWf8+8Fttp29GghsQBjYJVmWq3gBFfe9Hs1spR44vovn2wA==} engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' - '@types/debug': ^4.1.12 + '@opentelemetry/api': ^1.9.0 '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 - '@vitest/browser-playwright': 4.0.8 - '@vitest/browser-preview': 4.0.8 - '@vitest/browser-webdriverio': 4.0.8 - '@vitest/ui': 4.0.8 + '@vitest/browser-playwright': 4.0.15 + '@vitest/browser-preview': 4.0.15 + '@vitest/browser-webdriverio': 4.0.15 + '@vitest/ui': 4.0.15 happy-dom: '*' jsdom: '*' peerDependenciesMeta: '@edge-runtime/vm': optional: true - '@types/debug': + '@opentelemetry/api': optional: true '@types/node': optional: true @@ -9276,9 +9236,6 @@ packages: vlq@0.2.3: resolution: {integrity: sha512-DRibZL6DsNhIgYQ+wNdWDL2SL3bKPlVrRiBqV5yuMm++op8W4kGFtaQfCs4KEJn0wBZcHVHJ3eoywX8983k1ow==} - vscode-uri@3.1.0: - resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==} - walkdir@0.4.1: resolution: {integrity: sha512-3eBwRyEln6E1MSzcxcVpQIhRG8Q1jLvEqRmCZqS3dsfXEDR/AhOF4d+jHg1qvDCpYaVRZjENPQyrVxAkQqxPgQ==} engines: {node: '>=6.0.0'} @@ -9689,7 +9646,7 @@ snapshots: dependencies: '@babel/compat-data': 7.25.2 '@babel/helper-validator-option': 7.24.8 - browserslist: 4.28.1 + browserslist: 4.24.4 lru-cache: 5.1.1 semver: 6.3.1 @@ -9697,7 +9654,7 @@ snapshots: dependencies: '@babel/compat-data': 7.28.5 '@babel/helper-validator-option': 7.27.1 - browserslist: 4.28.1 + browserslist: 4.28.0 lru-cache: 5.1.1 semver: 6.3.1 @@ -10946,12 +10903,12 @@ snapshots: '@firebase/component@0.6.8': dependencies: '@firebase/util': 1.9.7 - tslib: 2.6.3 + tslib: 2.8.1 '@firebase/component@0.7.0': dependencies: '@firebase/util': 1.13.0 - tslib: 2.6.3 + tslib: 2.8.1 '@firebase/data-connect@0.3.11(@firebase/app@0.14.0)': dependencies: @@ -10998,7 +10955,7 @@ snapshots: '@firebase/logger': 0.4.2 '@firebase/util': 1.9.7 faye-websocket: 0.11.4 - tslib: 2.6.3 + tslib: 2.8.1 '@firebase/database@1.1.0': dependencies: @@ -11087,11 +11044,11 @@ snapshots: '@firebase/logger@0.4.2': dependencies: - tslib: 2.6.3 + tslib: 2.8.1 '@firebase/logger@0.5.0': dependencies: - tslib: 2.6.3 + tslib: 2.8.1 '@firebase/messaging-compat@0.2.23(@firebase/app-compat@0.5.0)(@firebase/app@0.14.0)': dependencies: @@ -11192,7 +11149,7 @@ snapshots: '@firebase/util@1.9.7': dependencies: - tslib: 2.6.3 + tslib: 2.8.1 '@firebase/webchannel-wrapper@1.0.4': {} @@ -11787,70 +11744,70 @@ snapshots: '@opentelemetry/semantic-conventions@1.34.0': {} - '@oxfmt/darwin-arm64@0.18.0': + '@oxfmt/darwin-arm64@0.19.0': optional: true - '@oxfmt/darwin-x64@0.18.0': + '@oxfmt/darwin-x64@0.19.0': optional: true - '@oxfmt/linux-arm64-gnu@0.18.0': + '@oxfmt/linux-arm64-gnu@0.19.0': optional: true - '@oxfmt/linux-arm64-musl@0.18.0': + '@oxfmt/linux-arm64-musl@0.19.0': optional: true - '@oxfmt/linux-x64-gnu@0.18.0': + '@oxfmt/linux-x64-gnu@0.19.0': optional: true - '@oxfmt/linux-x64-musl@0.18.0': + '@oxfmt/linux-x64-musl@0.19.0': optional: true - '@oxfmt/win32-arm64@0.18.0': + '@oxfmt/win32-arm64@0.19.0': optional: true - '@oxfmt/win32-x64@0.18.0': + '@oxfmt/win32-x64@0.19.0': optional: true - '@oxlint-tsgolint/darwin-arm64@0.9.0': + '@oxlint-tsgolint/darwin-arm64@0.9.2': optional: true - '@oxlint-tsgolint/darwin-x64@0.9.0': + '@oxlint-tsgolint/darwin-x64@0.9.2': optional: true - '@oxlint-tsgolint/linux-arm64@0.9.0': + '@oxlint-tsgolint/linux-arm64@0.9.2': optional: true - '@oxlint-tsgolint/linux-x64@0.9.0': + '@oxlint-tsgolint/linux-x64@0.9.2': optional: true - '@oxlint-tsgolint/win32-arm64@0.9.0': + '@oxlint-tsgolint/win32-arm64@0.9.2': optional: true - '@oxlint-tsgolint/win32-x64@0.9.0': + '@oxlint-tsgolint/win32-x64@0.9.2': optional: true - '@oxlint/darwin-arm64@1.33.0': + '@oxlint/darwin-arm64@1.34.0': optional: true - '@oxlint/darwin-x64@1.33.0': + '@oxlint/darwin-x64@1.34.0': optional: true - '@oxlint/linux-arm64-gnu@1.33.0': + '@oxlint/linux-arm64-gnu@1.34.0': optional: true - '@oxlint/linux-arm64-musl@1.33.0': + '@oxlint/linux-arm64-musl@1.34.0': optional: true - '@oxlint/linux-x64-gnu@1.33.0': + '@oxlint/linux-x64-gnu@1.34.0': optional: true - '@oxlint/linux-x64-musl@1.33.0': + '@oxlint/linux-x64-musl@1.34.0': optional: true - '@oxlint/win32-arm64@1.33.0': + '@oxlint/win32-arm64@1.34.0': optional: true - '@oxlint/win32-x64@1.33.0': + '@oxlint/win32-x64@1.34.0': optional: true '@paralleldrive/cuid2@2.2.2': @@ -12382,7 +12339,7 @@ snapshots: '@types/node': 24.9.1 '@types/ssh2': 1.15.5 - '@types/dockerode@3.3.42': + '@types/dockerode@3.3.47': dependencies: '@types/docker-modem': 3.0.6 '@types/node': 24.9.1 @@ -12597,85 +12554,102 @@ snapshots: '@typescript-eslint/types': 7.18.0 eslint-visitor-keys: 3.4.3 - '@vitest/coverage-v8@4.0.8(vitest@4.0.8(@types/node@20.5.1)(happy-dom@20.0.10)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1))': + '@vitest/coverage-v8@4.0.15(vitest@4.0.15(@opentelemetry/api@1.8.0)(@types/node@24.9.1)(happy-dom@20.0.10)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1))': dependencies: '@bcoe/v8-coverage': 1.0.2 - '@vitest/utils': 4.0.8 + '@vitest/utils': 4.0.15 ast-v8-to-istanbul: 0.3.8 - debug: 4.4.3 istanbul-lib-coverage: 3.2.2 istanbul-lib-report: 3.0.1 istanbul-lib-source-maps: 5.0.6 istanbul-reports: 3.2.0 magicast: 0.5.1 + obug: 2.1.1 std-env: 3.10.0 tinyrainbow: 3.0.3 - vitest: 4.0.8(@types/node@20.5.1)(happy-dom@20.0.10)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1) + vitest: 4.0.15(@opentelemetry/api@1.8.0)(@types/node@24.9.1)(happy-dom@20.0.10)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1) transitivePeerDependencies: - supports-color - '@vitest/coverage-v8@4.0.8(vitest@4.0.8(@types/node@24.9.1)(happy-dom@20.0.10)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1))': + '@vitest/coverage-v8@4.0.15(vitest@4.0.15(@types/node@20.5.1)(happy-dom@20.0.10)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1))': dependencies: '@bcoe/v8-coverage': 1.0.2 - '@vitest/utils': 4.0.8 + '@vitest/utils': 4.0.15 ast-v8-to-istanbul: 0.3.8 - debug: 4.4.3 istanbul-lib-coverage: 3.2.2 istanbul-lib-report: 3.0.1 istanbul-lib-source-maps: 5.0.6 istanbul-reports: 3.2.0 magicast: 0.5.1 + obug: 2.1.1 std-env: 3.10.0 tinyrainbow: 3.0.3 - vitest: 4.0.8(@types/node@24.9.1)(happy-dom@20.0.10)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1) + vitest: 4.0.15(@types/node@20.5.1)(happy-dom@20.0.10)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1) transitivePeerDependencies: - supports-color - '@vitest/expect@4.0.8': + '@vitest/coverage-v8@4.0.15(vitest@4.0.15(@types/node@24.9.1)(happy-dom@20.0.10)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1))': + dependencies: + '@bcoe/v8-coverage': 1.0.2 + '@vitest/utils': 4.0.15 + ast-v8-to-istanbul: 0.3.8 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 5.0.6 + istanbul-reports: 3.2.0 + magicast: 0.5.1 + obug: 2.1.1 + std-env: 3.10.0 + tinyrainbow: 3.0.3 + vitest: 4.0.15(@types/node@24.9.1)(happy-dom@20.0.10)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1) + transitivePeerDependencies: + - supports-color + + '@vitest/expect@4.0.15': dependencies: '@standard-schema/spec': 1.0.0 '@types/chai': 5.2.3 - '@vitest/spy': 4.0.8 - '@vitest/utils': 4.0.8 - chai: 6.2.0 + '@vitest/spy': 4.0.15 + '@vitest/utils': 4.0.15 + chai: 6.2.1 tinyrainbow: 3.0.3 - '@vitest/mocker@4.0.8(vite@7.1.12(@types/node@20.5.1)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1))': + '@vitest/mocker@4.0.15(vite@7.1.12(@types/node@20.5.1)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1))': dependencies: - '@vitest/spy': 4.0.8 + '@vitest/spy': 4.0.15 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: vite: 7.1.12(@types/node@20.5.1)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1) - '@vitest/mocker@4.0.8(vite@7.1.12(@types/node@24.9.1)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1))': + '@vitest/mocker@4.0.15(vite@7.1.12(@types/node@24.9.1)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1))': dependencies: - '@vitest/spy': 4.0.8 + '@vitest/spy': 4.0.15 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: vite: 7.1.12(@types/node@24.9.1)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1) - '@vitest/pretty-format@4.0.8': + '@vitest/pretty-format@4.0.15': dependencies: tinyrainbow: 3.0.3 - '@vitest/runner@4.0.8': + '@vitest/runner@4.0.15': dependencies: - '@vitest/utils': 4.0.8 + '@vitest/utils': 4.0.15 pathe: 2.0.3 - '@vitest/snapshot@4.0.8': + '@vitest/snapshot@4.0.15': dependencies: - '@vitest/pretty-format': 4.0.8 + '@vitest/pretty-format': 4.0.15 magic-string: 0.30.21 pathe: 2.0.3 - '@vitest/spy@4.0.8': {} + '@vitest/spy@4.0.15': {} - '@vitest/utils@4.0.8': + '@vitest/utils@4.0.15': dependencies: - '@vitest/pretty-format': 4.0.8 + '@vitest/pretty-format': 4.0.15 tinyrainbow: 3.0.3 '@vue/compiler-core@3.4.37': @@ -13016,9 +12990,6 @@ snapshots: balloon-css@1.2.0: {} - bare-events@2.4.2: - optional: true - bare-events@2.6.0: optional: true @@ -13046,7 +13017,7 @@ snapshots: base64-js@1.5.1: {} - baseline-browser-mapping@2.9.5: {} + baseline-browser-mapping@2.9.10: {} basic-auth-connect@1.0.0: {} @@ -13174,13 +13145,20 @@ snapshots: node-releases: 2.0.18 update-browserslist-db: 1.1.0(browserslist@4.23.3) - browserslist@4.28.1: + browserslist@4.24.4: dependencies: - baseline-browser-mapping: 2.9.5 - caniuse-lite: 1.0.30001759 + caniuse-lite: 1.0.30001715 + electron-to-chromium: 1.5.144 + node-releases: 2.0.19 + update-browserslist-db: 1.1.3(browserslist@4.24.4) + + browserslist@4.28.0: + dependencies: + baseline-browser-mapping: 2.9.10 + caniuse-lite: 1.0.30001761 electron-to-chromium: 1.5.267 node-releases: 2.0.27 - update-browserslist-db: 1.2.2(browserslist@4.28.1) + update-browserslist-db: 1.2.3(browserslist@4.28.0) bson@6.8.0: {} @@ -13287,7 +13265,7 @@ snapshots: camel-case@4.1.2: dependencies: pascal-case: 3.1.2 - tslib: 2.6.3 + tslib: 2.8.1 camelcase-keys@6.2.2: dependencies: @@ -13303,11 +13281,13 @@ snapshots: caniuse-lite@1.0.30001649: {} - caniuse-lite@1.0.30001759: {} + caniuse-lite@1.0.30001715: {} + + caniuse-lite@1.0.30001761: {} canvas-confetti@1.5.1: {} - chai@6.2.0: {} + chai@6.2.1: {} chalk@4.1.2: dependencies: @@ -13316,8 +13296,6 @@ snapshots: chalk@5.2.0: {} - chalk@5.3.0: {} - chalk@5.6.2: {} char-regex@1.0.2: {} @@ -13550,7 +13528,7 @@ snapshots: chalk: 4.1.2 date-fns: 2.30.0 lodash: 4.17.21 - rxjs: 7.8.1 + rxjs: 7.8.2 shell-quote: 1.8.1 spawn-command: 0.0.2 supports-color: 8.1.1 @@ -13700,7 +13678,7 @@ snapshots: core-js-compat@3.47.0: dependencies: - browserslist: 4.28.1 + browserslist: 4.28.0 core-js@3.37.1: {} @@ -13824,7 +13802,7 @@ snapshots: date-fns@2.30.0: dependencies: - '@babel/runtime': 7.25.0 + '@babel/runtime': 7.28.4 date-fns@3.6.0: {} @@ -14006,7 +13984,7 @@ snapshots: disposable-email-domains-js@1.16.0: {} - docker-compose@1.2.0: + docker-compose@1.3.0: dependencies: yaml: 2.8.1 @@ -14019,14 +13997,14 @@ snapshots: transitivePeerDependencies: - supports-color - dockerode@4.0.7: + dockerode@4.0.9: dependencies: '@balena/dockerignore': 1.0.2 '@grpc/grpc-js': 1.11.1 '@grpc/proto-loader': 0.7.13 docker-modem: 5.0.6 protobufjs: 7.3.2 - tar-fs: 2.1.3 + tar-fs: 2.1.4 uuid: 10.0.0 transitivePeerDependencies: - supports-color @@ -14076,7 +14054,7 @@ snapshots: dot-case@3.0.4: dependencies: no-case: 3.0.4 - tslib: 2.6.3 + tslib: 2.8.1 dot-prop@5.3.0: dependencies: @@ -14124,6 +14102,8 @@ snapshots: dependencies: jake: 10.9.4 + electron-to-chromium@1.5.144: {} + electron-to-chromium@1.5.267: {} electron-to-chromium@1.5.5: {} @@ -14377,13 +14357,13 @@ snapshots: dependencies: '@mdn/browser-compat-data': 5.7.6 ast-metadata-inferer: 0.8.1 - browserslist: 4.28.1 - caniuse-lite: 1.0.30001759 + browserslist: 4.24.4 + caniuse-lite: 1.0.30001715 eslint: 9.39.1 find-up: 5.0.0 globals: 15.15.0 lodash.memoize: 4.1.2 - semver: 7.7.3 + semver: 7.6.3 eslint-scope@8.4.0: dependencies: @@ -15537,7 +15517,7 @@ snapshots: inquirer: 8.2.6 picocolors: 1.1.1 run-async: 2.4.1 - rxjs: 7.8.1 + rxjs: 7.8.2 inquirer@8.2.6: dependencies: @@ -15551,7 +15531,7 @@ snapshots: mute-stream: 0.0.8 ora: 5.4.1 run-async: 2.4.1 - rxjs: 7.8.1 + rxjs: 7.8.2 string-width: 4.2.3 strip-ansi: 6.0.1 through: 2.3.8 @@ -15954,7 +15934,7 @@ snapshots: json-schema-to-ts@2.7.2: dependencies: - '@babel/runtime': 7.28.2 + '@babel/runtime': 7.28.4 '@types/json-schema': 7.0.15 ts-algebra: 1.2.2 @@ -16099,7 +16079,7 @@ snapshots: light-my-request@4.12.0: dependencies: - ajv: 8.17.1 + ajv: 8.12.0 cookie: 0.5.0 process-warning: 1.0.0 set-cookie-parser: 2.6.0 @@ -16241,7 +16221,7 @@ snapshots: lower-case@2.0.2: dependencies: - tslib: 2.6.3 + tslib: 2.8.1 lru-cache@10.4.3: {} @@ -16354,7 +16334,7 @@ snapshots: marked-terminal@7.1.0(marked@13.0.3): dependencies: ansi-escapes: 7.0.0 - chalk: 5.3.0 + chalk: 5.6.2 cli-highlight: 2.1.11 cli-table3: 0.6.5 marked: 13.0.3 @@ -16948,7 +16928,7 @@ snapshots: no-case@3.0.4: dependencies: lower-case: 2.0.2 - tslib: 2.6.3 + tslib: 2.8.1 node-addon-api@5.1.0: {} @@ -17004,6 +16984,8 @@ snapshots: node-releases@2.0.18: {} + node-releases@2.0.19: {} + node-releases@2.0.27: {} node-source-walk@7.0.0: @@ -17069,11 +17051,6 @@ snapshots: dependencies: path-key: 4.0.0 - npm-run-path@6.0.0: - dependencies: - path-key: 4.0.0 - unicorn-magic: 0.3.0 - npmlog@5.0.1: dependencies: are-we-there-yet: 2.0.0 @@ -17101,7 +17078,7 @@ snapshots: oas-kit-common: 1.0.8 reftools: 1.1.9 yaml: 1.10.2 - yargs: 17.0.1 + yargs: 17.7.2 oas-schema-walker@1.1.5: {} @@ -17137,6 +17114,8 @@ snapshots: has-symbols: 1.1.0 object-keys: 1.1.1 + obug@2.1.1: {} + ohash@2.0.11: {} on-finished@2.3.0: @@ -17203,7 +17182,7 @@ snapshots: openapi3-ts@3.2.0: dependencies: - yaml: 2.5.0 + yaml: 2.8.1 optionator@0.9.4: dependencies: @@ -17236,39 +17215,39 @@ snapshots: object-keys: 1.1.1 safe-push-apply: 1.0.0 - oxfmt@0.18.0: + oxfmt@0.19.0: dependencies: tinypool: 2.0.0 optionalDependencies: - '@oxfmt/darwin-arm64': 0.18.0 - '@oxfmt/darwin-x64': 0.18.0 - '@oxfmt/linux-arm64-gnu': 0.18.0 - '@oxfmt/linux-arm64-musl': 0.18.0 - '@oxfmt/linux-x64-gnu': 0.18.0 - '@oxfmt/linux-x64-musl': 0.18.0 - '@oxfmt/win32-arm64': 0.18.0 - '@oxfmt/win32-x64': 0.18.0 + '@oxfmt/darwin-arm64': 0.19.0 + '@oxfmt/darwin-x64': 0.19.0 + '@oxfmt/linux-arm64-gnu': 0.19.0 + '@oxfmt/linux-arm64-musl': 0.19.0 + '@oxfmt/linux-x64-gnu': 0.19.0 + '@oxfmt/linux-x64-musl': 0.19.0 + '@oxfmt/win32-arm64': 0.19.0 + '@oxfmt/win32-x64': 0.19.0 - oxlint-tsgolint@0.9.0: + oxlint-tsgolint@0.9.2: optionalDependencies: - '@oxlint-tsgolint/darwin-arm64': 0.9.0 - '@oxlint-tsgolint/darwin-x64': 0.9.0 - '@oxlint-tsgolint/linux-arm64': 0.9.0 - '@oxlint-tsgolint/linux-x64': 0.9.0 - '@oxlint-tsgolint/win32-arm64': 0.9.0 - '@oxlint-tsgolint/win32-x64': 0.9.0 + '@oxlint-tsgolint/darwin-arm64': 0.9.2 + '@oxlint-tsgolint/darwin-x64': 0.9.2 + '@oxlint-tsgolint/linux-arm64': 0.9.2 + '@oxlint-tsgolint/linux-x64': 0.9.2 + '@oxlint-tsgolint/win32-arm64': 0.9.2 + '@oxlint-tsgolint/win32-x64': 0.9.2 - oxlint@1.33.0(oxlint-tsgolint@0.9.0): + oxlint@1.34.0(oxlint-tsgolint@0.9.2): optionalDependencies: - '@oxlint/darwin-arm64': 1.33.0 - '@oxlint/darwin-x64': 1.33.0 - '@oxlint/linux-arm64-gnu': 1.33.0 - '@oxlint/linux-arm64-musl': 1.33.0 - '@oxlint/linux-x64-gnu': 1.33.0 - '@oxlint/linux-x64-musl': 1.33.0 - '@oxlint/win32-arm64': 1.33.0 - '@oxlint/win32-x64': 1.33.0 - oxlint-tsgolint: 0.9.0 + '@oxlint/darwin-arm64': 1.34.0 + '@oxlint/darwin-x64': 1.34.0 + '@oxlint/linux-arm64-gnu': 1.34.0 + '@oxlint/linux-arm64-musl': 1.34.0 + '@oxlint/linux-x64-gnu': 1.34.0 + '@oxlint/linux-x64-musl': 1.34.0 + '@oxlint/win32-arm64': 1.34.0 + '@oxlint/win32-x64': 1.34.0 + oxlint-tsgolint: 0.9.2 p-defer@3.0.0: {} @@ -17327,7 +17306,7 @@ snapshots: param-case@3.0.4: dependencies: dot-case: 3.0.4 - tslib: 2.6.3 + tslib: 2.8.1 parent-module@1.0.1: dependencies: @@ -17372,7 +17351,7 @@ snapshots: pascal-case@3.1.2: dependencies: no-case: 3.0.4 - tslib: 2.6.3 + tslib: 2.8.1 path-browserify@1.0.1: {} @@ -17696,8 +17675,6 @@ snapshots: queue-microtask@1.2.3: {} - queue-tick@1.0.1: {} - quick-format-unescaped@4.0.4: {} quick-lru@4.0.1: {} @@ -18106,10 +18083,6 @@ snapshots: dependencies: queue-microtask: 1.2.3 - rxjs@7.8.1: - dependencies: - tslib: 2.6.3 - rxjs@7.8.2: dependencies: tslib: 2.8.1 @@ -18591,21 +18564,12 @@ snapshots: dependencies: any-promise: 1.3.0 - streamx@2.18.0: - dependencies: - fast-fifo: 1.3.2 - queue-tick: 1.0.1 - text-decoder: 1.1.1 - optionalDependencies: - bare-events: 2.4.2 - streamx@2.22.1: dependencies: fast-fifo: 1.3.2 text-decoder: 1.1.1 optionalDependencies: bare-events: 2.6.0 - optional: true string-argv@0.3.2: {} @@ -18847,20 +18811,20 @@ snapshots: oas-validator: 5.0.8 reftools: 1.1.9 yaml: 1.10.2 - yargs: 17.0.1 + yargs: 17.7.2 transitivePeerDependencies: - encoding tapable@2.2.1: {} - tar-fs@2.1.3: + tar-fs@2.1.4: dependencies: chownr: 1.1.4 mkdirp-classic: 0.5.3 pump: 3.0.0 tar-stream: 2.2.0 - tar-fs@3.1.0: + tar-fs@3.1.1: dependencies: pump: 3.0.0 tar-stream: 3.1.7 @@ -18882,7 +18846,7 @@ snapshots: dependencies: b4a: 1.6.6 fast-fifo: 1.3.2 - streamx: 2.18.0 + streamx: 2.22.1 tar@6.2.1: dependencies: @@ -18938,23 +18902,23 @@ snapshots: commander: 2.20.3 source-map-support: 0.5.21 - testcontainers@11.4.0: + testcontainers@11.10.0: dependencies: '@balena/dockerignore': 1.0.2 - '@types/dockerode': 3.3.42 + '@types/dockerode': 3.3.47 archiver: 7.0.1 async-lock: 1.4.1 byline: 5.0.0 - debug: 4.4.1 - docker-compose: 1.2.0 - dockerode: 4.0.7 + debug: 4.4.3 + docker-compose: 1.3.0 + dockerode: 4.0.9 get-port: 7.1.0 proper-lockfile: 4.1.2 properties-reader: 2.3.0 ssh-remote-port-forward: 1.0.4 - tar-fs: 3.1.0 - tmp: 0.2.3 - undici: 7.12.0 + tar-fs: 3.1.1 + tmp: 0.2.5 + undici: 7.16.0 transitivePeerDependencies: - bare-buffer - supports-color @@ -18985,14 +18949,14 @@ snapshots: through@2.3.8: {} - tiny-invariant@1.3.3: {} - tiny-lru@8.0.2: {} tinybench@2.9.0: {} tinyexec@0.3.2: {} + tinyexec@1.0.2: {} + tinyglobby@0.2.13: dependencies: fdir: 6.4.4(picomatch@4.0.3) @@ -19254,7 +19218,7 @@ snapshots: undici@6.21.3: {} - undici@7.12.0: {} + undici@7.16.0: {} unescape-js@1.1.4: dependencies: @@ -19275,8 +19239,6 @@ snapshots: unicorn-magic@0.1.0: {} - unicorn-magic@0.3.0: {} - unique-filename@3.0.0: dependencies: unique-slug: 4.0.0 @@ -19335,9 +19297,15 @@ snapshots: escalade: 3.2.0 picocolors: 1.0.1 - update-browserslist-db@1.2.2(browserslist@4.28.1): + update-browserslist-db@1.1.3(browserslist@4.24.4): dependencies: - browserslist: 4.28.1 + browserslist: 4.24.4 + escalade: 3.2.0 + picocolors: 1.1.1 + + update-browserslist-db@1.2.3(browserslist@4.28.0): + dependencies: + browserslist: 4.28.0 escalade: 3.2.0 picocolors: 1.1.1 @@ -19422,24 +19390,6 @@ snapshots: dependencies: vite: 7.1.12(@types/node@24.9.1)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1) - vite-plugin-checker@0.11.0(eslint@9.39.1)(meow@13.2.0)(optionator@0.9.4)(oxlint@1.33.0(oxlint-tsgolint@0.9.0))(typescript@5.9.3)(vite@7.1.12(@types/node@24.9.1)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1)): - dependencies: - '@babel/code-frame': 7.27.1 - chokidar: 4.0.3 - npm-run-path: 6.0.0 - picocolors: 1.1.1 - picomatch: 4.0.3 - tiny-invariant: 1.3.3 - tinyglobby: 0.2.15 - vite: 7.1.12(@types/node@24.9.1)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1) - vscode-uri: 3.1.0 - optionalDependencies: - eslint: 9.39.1 - meow: 13.2.0 - optionator: 0.9.4 - oxlint: 1.33.0(oxlint-tsgolint@0.9.0) - typescript: 5.9.3 - vite-plugin-filter-replace@0.1.14: dependencies: magic-string: 0.30.17 @@ -19510,24 +19460,63 @@ snapshots: tsx: 4.16.2 yaml: 2.8.1 - vitest@4.0.8(@types/node@20.5.1)(happy-dom@20.0.10)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1): + vitest@4.0.15(@opentelemetry/api@1.8.0)(@types/node@24.9.1)(happy-dom@20.0.10)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1): dependencies: - '@vitest/expect': 4.0.8 - '@vitest/mocker': 4.0.8(vite@7.1.12(@types/node@20.5.1)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1)) - '@vitest/pretty-format': 4.0.8 - '@vitest/runner': 4.0.8 - '@vitest/snapshot': 4.0.8 - '@vitest/spy': 4.0.8 - '@vitest/utils': 4.0.8 - debug: 4.4.3 + '@vitest/expect': 4.0.15 + '@vitest/mocker': 4.0.15(vite@7.1.12(@types/node@24.9.1)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1)) + '@vitest/pretty-format': 4.0.15 + '@vitest/runner': 4.0.15 + '@vitest/snapshot': 4.0.15 + '@vitest/spy': 4.0.15 + '@vitest/utils': 4.0.15 es-module-lexer: 1.7.0 expect-type: 1.2.2 magic-string: 0.30.21 + obug: 2.1.1 pathe: 2.0.3 picomatch: 4.0.3 std-env: 3.10.0 tinybench: 2.9.0 - tinyexec: 0.3.2 + tinyexec: 1.0.2 + tinyglobby: 0.2.15 + tinyrainbow: 3.0.3 + vite: 7.1.12(@types/node@24.9.1)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1) + why-is-node-running: 2.3.0 + optionalDependencies: + '@opentelemetry/api': 1.8.0 + '@types/node': 24.9.1 + happy-dom: 20.0.10 + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - terser + - tsx + - yaml + + vitest@4.0.15(@types/node@20.5.1)(happy-dom@20.0.10)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1): + dependencies: + '@vitest/expect': 4.0.15 + '@vitest/mocker': 4.0.15(vite@7.1.12(@types/node@20.5.1)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1)) + '@vitest/pretty-format': 4.0.15 + '@vitest/runner': 4.0.15 + '@vitest/snapshot': 4.0.15 + '@vitest/spy': 4.0.15 + '@vitest/utils': 4.0.15 + es-module-lexer: 1.7.0 + expect-type: 1.2.2 + magic-string: 0.30.21 + obug: 2.1.1 + pathe: 2.0.3 + picomatch: 4.0.3 + std-env: 3.10.0 + tinybench: 2.9.0 + tinyexec: 1.0.2 tinyglobby: 0.2.15 tinyrainbow: 3.0.3 vite: 7.1.12(@types/node@20.5.1)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1) @@ -19544,29 +19533,28 @@ snapshots: - sass-embedded - stylus - sugarss - - supports-color - terser - tsx - yaml - vitest@4.0.8(@types/node@24.9.1)(happy-dom@20.0.10)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1): + vitest@4.0.15(@types/node@24.9.1)(happy-dom@20.0.10)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1): dependencies: - '@vitest/expect': 4.0.8 - '@vitest/mocker': 4.0.8(vite@7.1.12(@types/node@24.9.1)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1)) - '@vitest/pretty-format': 4.0.8 - '@vitest/runner': 4.0.8 - '@vitest/snapshot': 4.0.8 - '@vitest/spy': 4.0.8 - '@vitest/utils': 4.0.8 - debug: 4.4.3 + '@vitest/expect': 4.0.15 + '@vitest/mocker': 4.0.15(vite@7.1.12(@types/node@24.9.1)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1)) + '@vitest/pretty-format': 4.0.15 + '@vitest/runner': 4.0.15 + '@vitest/snapshot': 4.0.15 + '@vitest/spy': 4.0.15 + '@vitest/utils': 4.0.15 es-module-lexer: 1.7.0 expect-type: 1.2.2 magic-string: 0.30.21 + obug: 2.1.1 pathe: 2.0.3 picomatch: 4.0.3 std-env: 3.10.0 tinybench: 2.9.0 - tinyexec: 0.3.2 + tinyexec: 1.0.2 tinyglobby: 0.2.15 tinyrainbow: 3.0.3 vite: 7.1.12(@types/node@24.9.1)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1) @@ -19583,15 +19571,12 @@ snapshots: - sass-embedded - stylus - sugarss - - supports-color - terser - tsx - yaml vlq@0.2.3: {} - vscode-uri@3.1.0: {} - walkdir@0.4.1: {} wawoff2@2.0.1: