refactor: project saving logic (#5225)

* fix: project data saving not visually shown immediately

* feat: useSavable improvements

* feat: migrate where possible to useSavable

* fix: gitignore

* feat: use es-toolkit
This commit is contained in:
Calum H.
2026-01-28 16:46:14 +00:00
committed by GitHub
parent e57c15b3ce
commit 400c571fe6
15 changed files with 699 additions and 507 deletions

1
.gitignore vendored
View File

@@ -65,6 +65,7 @@ app-playground-data/*
.astro
.claude
.letta
# labrinth demo fixtures
apps/labrinth/fixtures/demo

View File

@@ -1647,6 +1647,15 @@ async function resetVersions() {
await resetVersionsV3()
}
// Helper to invalidate project queries after mutations settle
async function invalidateProjectQueries(projectId) {
await queryClient.invalidateQueries({ queryKey: ['project', 'v2', routeProjectId.value] })
if (routeProjectId.value !== projectId) {
await queryClient.invalidateQueries({ queryKey: ['project', 'v2', projectId] })
}
await queryClient.invalidateQueries({ queryKey: ['project', 'v3', projectId] })
}
// Mutation for patching project data
const patchProjectMutation = useMutation({
mutationFn: async ({ projectId, data }) => {
@@ -1691,12 +1700,7 @@ const patchProjectMutation = useMutation({
},
onSettled: async (_data, _error, { projectId }) => {
// Invalidate both slug-based and ID-based cache keys to ensure consistency
await queryClient.invalidateQueries({ queryKey: ['project', 'v2', routeProjectId.value] })
if (routeProjectId.value !== projectId) {
await queryClient.invalidateQueries({ queryKey: ['project', 'v2', projectId] })
}
await queryClient.invalidateQueries({ queryKey: ['project', 'v3', projectId] })
await invalidateProjectQueries(projectId)
},
})
@@ -1741,11 +1745,7 @@ const patchStatusMutation = useMutation({
},
onSettled: async (_data, _error, { projectId }) => {
// Invalidate both slug-based and ID-based cache keys to ensure consistency
await queryClient.invalidateQueries({ queryKey: ['project', 'v2', routeProjectId.value] })
if (routeProjectId.value !== projectId) {
await queryClient.invalidateQueries({ queryKey: ['project', 'v2', projectId] })
}
await invalidateProjectQueries(projectId)
},
})
@@ -1779,8 +1779,171 @@ const patchIconMutation = useMutation({
},
onSettled: async (_data, _error, { projectId }) => {
await queryClient.invalidateQueries({ queryKey: ['project', 'v2', projectId] })
await queryClient.invalidateQueries({ queryKey: ['project', 'v3', projectId] })
await invalidateProjectQueries(projectId)
},
})
const createGalleryItemMutation = useMutation({
mutationFn: async ({ projectId, file, title, description, featured, ordering }) => {
let url = `project/${projectId}/gallery?ext=${
file.type.split('/')[file.type.split('/').length - 1]
}&featured=${featured ?? false}`
if (title) {
url += `&title=${encodeURIComponent(title)}`
}
if (description) {
url += `&description=${encodeURIComponent(description)}`
}
if (ordering !== null && ordering !== undefined) {
url += `&ordering=${ordering}`
}
await useBaseFetch(url, {
method: 'POST',
body: file,
})
},
onMutate: async ({ title, description, featured, ordering }) => {
await queryClient.cancelQueries({ queryKey: ['project', 'v2', routeProjectId.value] })
const previousProject = queryClient.getQueryData(['project', 'v2', routeProjectId.value])
queryClient.setQueryData(['project', 'v2', routeProjectId.value], (old) => {
if (!old) return old
const newItem = {
url: '',
raw_url: '',
featured: featured ?? false,
title: title ?? '',
description: description ?? '',
created: new Date().toISOString(),
ordering: ordering ?? old.gallery.length,
}
return {
...old,
gallery: [...old.gallery, newItem],
}
})
return { previousProject }
},
onError: (err, _variables, context) => {
if (context?.previousProject) {
queryClient.setQueryData(['project', 'v2', routeProjectId.value], context.previousProject)
}
addNotification({
title: formatMessage(commonMessages.errorNotificationTitle),
text: err.data ? err.data.description : err.message,
type: 'error',
})
},
onSettled: async (_data, _error, { projectId }) => {
await invalidateProjectQueries(projectId)
},
})
const editGalleryItemMutation = useMutation({
mutationFn: async ({ projectId, imageUrl, title, description, featured, ordering }) => {
let url = `project/${projectId}/gallery?url=${encodeURIComponent(imageUrl)}&featured=${featured ?? false}`
if (title) {
url += `&title=${encodeURIComponent(title)}`
}
if (description) {
url += `&description=${encodeURIComponent(description)}`
}
if (ordering !== null && ordering !== undefined) {
url += `&ordering=${ordering}`
}
await useBaseFetch(url, {
method: 'PATCH',
})
},
onMutate: async ({ imageUrl, title, description, featured, ordering }) => {
await queryClient.cancelQueries({ queryKey: ['project', 'v2', routeProjectId.value] })
const previousProject = queryClient.getQueryData(['project', 'v2', routeProjectId.value])
queryClient.setQueryData(['project', 'v2', routeProjectId.value], (old) => {
if (!old) return old
return {
...old,
gallery: old.gallery.map((item) => {
if (item.url === imageUrl) {
return {
...item,
title: title ?? item.title,
description: description ?? item.description,
featured: featured ?? item.featured,
ordering: ordering ?? item.ordering,
}
}
return item
}),
}
})
return { previousProject }
},
onError: (err, _variables, context) => {
if (context?.previousProject) {
queryClient.setQueryData(['project', 'v2', routeProjectId.value], context.previousProject)
}
addNotification({
title: formatMessage(commonMessages.errorNotificationTitle),
text: err.data ? err.data.description : err.message,
type: 'error',
})
},
onSettled: async (_data, _error, { projectId }) => {
await invalidateProjectQueries(projectId)
},
})
const deleteGalleryItemMutation = useMutation({
mutationFn: async ({ projectId, imageUrl }) => {
await useBaseFetch(`project/${projectId}/gallery?url=${encodeURIComponent(imageUrl)}`, {
method: 'DELETE',
})
},
onMutate: async ({ imageUrl }) => {
await queryClient.cancelQueries({ queryKey: ['project', 'v2', routeProjectId.value] })
const previousProject = queryClient.getQueryData(['project', 'v2', routeProjectId.value])
queryClient.setQueryData(['project', 'v2', routeProjectId.value], (old) => {
if (!old) return old
return {
...old,
gallery: old.gallery.filter((item) => item.url !== imageUrl),
}
})
return { previousProject }
},
onError: (err, _variables, context) => {
if (context?.previousProject) {
queryClient.setQueryData(['project', 'v2', routeProjectId.value], context.previousProject)
}
addNotification({
title: formatMessage(commonMessages.errorNotificationTitle),
text: err.data ? err.data.description : err.message,
type: 'error',
})
},
onSettled: async (_data, _error, { projectId }) => {
await invalidateProjectQueries(projectId)
},
})
@@ -1971,6 +2134,51 @@ async function patchIcon(icon) {
})
}
async function createGalleryItem(file, title, description, featured, ordering) {
startLoading()
return new Promise((resolve) => {
createGalleryItemMutation.mutate(
{ projectId: project.value.id, file, title, description, featured, ordering },
{
onSuccess: () => resolve(true),
onError: () => resolve(false),
onSettled: () => stopLoading(),
},
)
})
}
async function editGalleryItem(imageUrl, title, description, featured, ordering) {
startLoading()
return new Promise((resolve) => {
editGalleryItemMutation.mutate(
{ projectId: project.value.id, imageUrl, title, description, featured, ordering },
{
onSuccess: () => resolve(true),
onError: () => resolve(false),
onSettled: () => stopLoading(),
},
)
})
}
async function deleteGalleryItem(imageUrl) {
startLoading()
return new Promise((resolve) => {
deleteGalleryItemMutation.mutate(
{ projectId: project.value.id, imageUrl },
{
onSuccess: () => resolve(true),
onError: () => resolve(false),
onSettled: () => stopLoading(),
},
)
})
}
async function refreshMembers() {
// Simply invalidate and refetch - the computed allMembers will auto-update
await queryClient.invalidateQueries({ queryKey: ['project', projectId.value, 'members'] })
@@ -2103,6 +2311,11 @@ provideProjectPageContext({
patchProject,
patchIcon,
setProcessing,
// Gallery mutation functions
createGalleryItem,
editGalleryItem,
deleteGalleryItem,
})
</script>

View File

@@ -14,7 +14,7 @@
</span>
</div>
<MarkdownEditor
v-model="description"
v-model="current.description"
:disabled="
!currentMember ||
(currentMember?.permissions! & TeamMemberPermission.EDIT_BODY) !==
@@ -26,36 +26,42 @@
<TriangleAlertIcon class="my-auto" />
{{ descriptionWarning }}
</div>
<div class="input-group markdown-disclaimer">
<button
:disabled="!hasChanges"
class="iconified-button brand-button"
type="button"
@click="saveChanges()"
>
<SaveIcon />
Save changes
</button>
</div>
</div>
<UnsavedChangesPopup
:original="saved"
:modified="current"
:saving="saving"
@reset="reset"
@save="save"
/>
</div>
</template>
<script lang="ts" setup>
import { SaveIcon, TriangleAlertIcon } from '@modrinth/assets'
import { TriangleAlertIcon } from '@modrinth/assets'
import { countText, MIN_DESCRIPTION_CHARS } from '@modrinth/moderation'
import { injectProjectPageContext, MarkdownEditor } from '@modrinth/ui'
import {
injectProjectPageContext,
MarkdownEditor,
UnsavedChangesPopup,
useSavable,
} from '@modrinth/ui'
import { TeamMemberPermission } from '@modrinth/utils'
import { computed, ref } from 'vue'
import { computed } from 'vue'
import { useImageUpload } from '~/composables/image-upload.ts'
const { projectV2: project, currentMember, patchProject } = injectProjectPageContext()
const description = ref(project.value.body)
const { saved, current, saving, reset, save } = useSavable(
() => ({ description: project.value.body }),
async ({ description }) => {
await patchProject({ body: description })
},
)
const descriptionWarning = computed(() => {
const text = description.value?.trim() || ''
const text = current.value.description?.trim() || ''
const charCount = countText(text)
if (charCount < MIN_DESCRIPTION_CHARS) {
@@ -65,26 +71,6 @@ const descriptionWarning = computed(() => {
return null
})
const patchRequestPayload = computed(() => {
const payload: {
body?: string
} = {}
if (description.value !== project.value.body) {
payload.body = description.value
}
return payload
})
const hasChanges = computed(() => {
return Object.keys(patchRequestPayload.value).length > 0
})
function saveChanges() {
patchProject(patchRequestPayload.value)
}
async function onUploadHandler(file: File) {
const response = await useImageUpload(file, {
context: 'project',

View File

@@ -299,15 +299,19 @@ import {
ConfirmModal,
DropArea,
FileInput,
injectNotificationManager,
injectProjectPageContext,
NewModal as Modal,
} from '@modrinth/ui'
import { isPermission } from '~/utils/permissions.ts'
const { addNotification } = injectNotificationManager()
const { projectV2: project, currentMember, refreshProject } = injectProjectPageContext()
const {
projectV2: project,
currentMember,
createGalleryItem: createGalleryItemMutation,
editGalleryItem: editGalleryItemMutation,
deleteGalleryItem: deleteGalleryItemMutation,
} = injectProjectPageContext()
const title = `${project.value.title} - Gallery`
const description = `View ${project.value.gallery?.length ?? 0} images of ${project.value.title} on Modrinth.`
@@ -391,103 +395,42 @@ const showPreviewImage = () => {
const createGalleryItem = async () => {
shouldPreventActions.value = true
startLoading()
try {
let url = `project/${project.value.id}/gallery?ext=${
editFile.value
? editFile.value.type.split('/')[editFile.value.type.split('/').length - 1]
: null
}&featured=${editFeatured.value}`
if (editTitle.value) {
url += `&title=${encodeURIComponent(editTitle.value)}`
}
if (editDescription.value) {
url += `&description=${encodeURIComponent(editDescription.value)}`
}
if (editOrder.value) {
url += `&ordering=${editOrder.value}`
}
await useBaseFetch(url, {
method: 'POST',
body: editFile.value,
})
await refreshProject()
const success = await createGalleryItemMutation(
editFile.value,
editTitle.value || undefined,
editDescription.value || undefined,
editFeatured.value,
editOrder.value ?? undefined,
)
if (success) {
modal_edit_item.value.hide()
} catch (err) {
addNotification({
title: 'An error occurred',
text: err.data ? err.data.description : err,
type: 'error',
})
}
stopLoading()
shouldPreventActions.value = false
}
const editGalleryItem = async () => {
shouldPreventActions.value = true
startLoading()
try {
let url = `project/${project.value.id}/gallery?url=${encodeURIComponent(
project.value.gallery[editIndex.value].url,
)}&featured=${editFeatured.value}`
if (editTitle.value) {
url += `&title=${encodeURIComponent(editTitle.value)}`
}
if (editDescription.value) {
url += `&description=${encodeURIComponent(editDescription.value)}`
}
if (editOrder.value) {
url += `&ordering=${editOrder.value}`
}
const success = await editGalleryItemMutation(
project.value.gallery[editIndex.value].url,
editTitle.value || undefined,
editDescription.value || undefined,
editFeatured.value,
editOrder.value ?? undefined,
)
await useBaseFetch(url, {
method: 'PATCH',
})
await refreshProject()
if (success) {
modal_edit_item.value.hide()
} catch (err) {
addNotification({
title: 'An error occurred',
text: err.data ? err.data.description : err,
type: 'error',
})
}
stopLoading()
shouldPreventActions.value = false
}
const deleteGalleryImage = async () => {
startLoading()
try {
await useBaseFetch(
`project/${project.value.id}/gallery?url=${encodeURIComponent(
project.value.gallery[deleteIndex.value].url,
)}`,
{
method: 'DELETE',
},
)
await refreshProject()
} catch (err) {
addNotification({
title: 'An error occurred',
text: err.data ? err.data.description : err,
type: 'error',
})
}
stopLoading()
await deleteGalleryItemMutation(project.value.gallery[deleteIndex.value].url)
}
const handleKeydown = (e) => {

View File

@@ -2,8 +2,6 @@
import {
defineMessages,
IconSelect,
injectModrinthClient,
injectNotificationManager,
injectProjectPageContext,
type MessageDescriptor,
SettingsLabel,
@@ -14,34 +12,21 @@ import {
const { formatMessage } = useVIntl()
const { projectV2: project, refreshProject } = injectProjectPageContext()
const { handleError } = injectNotificationManager()
const client = injectModrinthClient()
const { projectV2: project, patchProject } = injectProjectPageContext()
const saving = ref(false)
const { saved, current, reset, save } = useSavable(
const { saved, current, saving, reset, save } = useSavable(
() => ({
title: project.value.title,
tagline: project.value.description,
url: project.value.slug,
icon: project.value.icon_url,
}),
({ title, tagline, url }) => {
const data: Record<string, string> = {
async ({ title, tagline, url }) => {
await patchProject({
...(title !== undefined && { title }),
...(tagline !== undefined && { description: tagline }),
...(url !== undefined && { slug: url }),
}
if (data) {
saving.value = true
client.labrinth.projects_v2
.edit(project.value.id, { title, description: tagline, slug: url })
.then(() => refreshProject().then(reset))
.catch(handleError)
.finally(() => (saving.value = false))
}
})
},
)

View File

@@ -28,7 +28,7 @@
<div class="w-1/2">
<DropdownSelect
v-model="license"
v-model="current.license"
name="License selector"
:options="builtinLicenses"
:display-name="(chosen: BuiltinLicense) => chosen.friendly"
@@ -37,7 +37,7 @@
</div>
</div>
<div v-if="license.requiresOnlyOrLater" class="adjacent-input">
<div v-if="current.license.requiresOnlyOrLater" class="adjacent-input">
<label for="or-later-checkbox">
<span class="label__title">Later editions</span>
<span class="label__description">
@@ -48,7 +48,7 @@
<Checkbox
id="or-later-checkbox"
v-model="allowOrLater"
v-model="current.allowOrLater"
:disabled="!hasPermission"
description="Allow later editions"
class="w-1/2"
@@ -60,7 +60,7 @@
<div class="adjacent-input">
<label for="license-url">
<span class="label__title">License URL</span>
<span v-if="license?.friendly !== 'Custom'" class="label__description">
<span v-if="current.license?.friendly !== 'Custom'" class="label__description">
The web location of the full license text. If you don't provide a link, the license text
will be displayed instead.
</span>
@@ -73,18 +73,20 @@
<div class="w-1/2">
<input
id="license-url"
v-model="licenseUrl"
v-model="current.licenseUrl"
type="url"
maxlength="2048"
:placeholder="license?.friendly !== 'Custom' ? `License URL (optional)` : `License URL`"
:placeholder="
current.license?.friendly !== 'Custom' ? `License URL (optional)` : `License URL`
"
:disabled="!hasPermission || licenseId === 'LicenseRef-Unknown'"
class="w-full"
/>
</div>
</div>
<div v-if="license?.friendly === 'Custom'" class="adjacent-input">
<label v-if="!nonSpdxLicense" for="license-spdx">
<div v-if="current.license?.friendly === 'Custom'" class="adjacent-input">
<label v-if="!current.nonSpdxLicense" for="license-spdx">
<span class="label__title">SPDX identifier</span>
<span class="label__description">
If your license does not have an offical
@@ -93,7 +95,7 @@
>, check the box and enter the name of the license instead.
</span>
</label>
<label v-else for="license-name">
<label v-if="current.nonSpdxLicense" for="license-name">
<span class="label__title">License name</span>
<span class="label__description"
>The full name of the license. If the license has a SPDX identifier, please uncheck the
@@ -103,9 +105,9 @@
<div class="input-stack w-1/2">
<input
v-if="!nonSpdxLicense"
v-if="!current.nonSpdxLicense"
id="license-spdx"
v-model="license.short"
v-model="current.license.short"
class="w-full"
type="text"
maxlength="128"
@@ -115,7 +117,7 @@
<input
v-else
id="license-name"
v-model="license.short"
v-model="current.license.short"
class="w-full"
type="text"
maxlength="128"
@@ -124,8 +126,8 @@
/>
<Checkbox
v-if="license?.friendly === 'Custom'"
v-model="nonSpdxLicense"
v-if="current.license?.friendly === 'Custom'"
v-model="current.nonSpdxLicense"
:disabled="!hasPermission"
description="License does not have a SPDX identifier"
>
@@ -133,74 +135,91 @@
</Checkbox>
</div>
</div>
<div class="input-stack">
<button
type="button"
class="iconified-button brand-button"
:disabled="
!hasChanges ||
!hasPermission ||
(license.friendly === 'Custom' && (license.short === '' || licenseUrl === ''))
"
@click="saveChanges()"
>
<SaveIcon />
Save changes
</button>
</div>
</section>
<UnsavedChangesPopup
:original="saved"
:modified="current"
:saving="saving"
:can-save="
hasPermission &&
!(
current.license.friendly === 'Custom' &&
(current.license.short === '' || current.licenseUrl === '')
)
"
@reset="reset"
@save="save"
/>
</div>
</template>
<script setup lang="ts">
import { SaveIcon } from '@modrinth/assets'
import { Checkbox, DropdownSelect, injectProjectPageContext } from '@modrinth/ui'
import {
Checkbox,
DropdownSelect,
injectProjectPageContext,
UnsavedChangesPopup,
useSavable,
} from '@modrinth/ui'
import {
type BuiltinLicense,
builtinLicenses,
formatProjectType,
TeamMemberPermission,
} from '@modrinth/utils'
import { computed, type Ref, ref } from 'vue'
import { computed } from 'vue'
const { projectV2: project, currentMember, patchProject } = injectProjectPageContext()
const licenseUrl = ref(project.value.license.url)
const license: Ref<{
friendly: string
short: string
requiresOnlyOrLater?: boolean
}> = ref({
friendly: '',
short: '',
requiresOnlyOrLater: false,
})
function getInitialLicense() {
const oldLicenseId = project.value.license.id
const trimmedLicenseId = oldLicenseId
.replaceAll('-only', '')
.replaceAll('-or-later', '')
.replaceAll('LicenseRef-', '')
const allowOrLater = ref(project.value.license.id.includes('-or-later'))
const nonSpdxLicense = ref(project.value.license.id.includes('LicenseRef-'))
const oldLicenseId = project.value.license.id
const trimmedLicenseId = oldLicenseId
.replaceAll('-only', '')
.replaceAll('-or-later', '')
.replaceAll('LicenseRef-', '')
license.value = builtinLicenses.find((x) => x.short === trimmedLicenseId) ?? {
friendly: 'Custom',
short: oldLicenseId.replaceAll('LicenseRef-', ''),
requiresOnlyOrLater: oldLicenseId.includes('-or-later'),
}
if (oldLicenseId === 'LicenseRef-Unknown') {
// Mark it as not having a license, forcing the user to select one
license.value = {
friendly: '',
short: oldLicenseId.replaceAll('LicenseRef-', ''),
requiresOnlyOrLater: false,
if (oldLicenseId === 'LicenseRef-Unknown') {
return {
friendly: '',
short: oldLicenseId.replaceAll('LicenseRef-', ''),
requiresOnlyOrLater: false,
}
}
return (
builtinLicenses.find((x) => x.short === trimmedLicenseId) ?? {
friendly: 'Custom',
short: oldLicenseId.replaceAll('LicenseRef-', ''),
requiresOnlyOrLater: oldLicenseId.includes('-or-later'),
}
)
}
const { saved, current, saving, reset, save } = useSavable(
() => ({
license: getInitialLicense(),
licenseUrl: project.value.license.url ?? '',
allowOrLater: project.value.license.id.includes('-or-later'),
nonSpdxLicense: project.value.license.id.includes('LicenseRef-'),
}),
async () => {
const payload: {
license_id?: string
license_url?: string | null
} = {}
if (licenseId.value !== project.value.license.id) {
payload.license_id = licenseId.value
}
if (current.value.licenseUrl !== project.value.license.url) {
payload.license_url = current.value.licenseUrl ? current.value.licenseUrl : null
}
await patchProject(payload)
},
)
const hasPermission = computed(() => {
return (currentMember.value?.permissions ?? 0) & TeamMemberPermission.EDIT_DETAILS
})
@@ -209,47 +228,22 @@ const licenseId = computed(() => {
let id = ''
if (
(nonSpdxLicense.value && license.value.friendly === 'Custom') ||
license.value.short === 'All-Rights-Reserved' ||
license.value.short === 'Unknown'
(current.value.nonSpdxLicense && current.value.license.friendly === 'Custom') ||
current.value.license.short === 'All-Rights-Reserved' ||
current.value.license.short === 'Unknown'
) {
id += 'LicenseRef-'
}
id += license.value.short
if (license.value.requiresOnlyOrLater) {
id += allowOrLater.value ? '-or-later' : '-only'
id += current.value.license.short
if (current.value.license.requiresOnlyOrLater) {
id += current.value.allowOrLater ? '-or-later' : '-only'
}
if (nonSpdxLicense.value && license.value.friendly === 'Custom') {
if (current.value.nonSpdxLicense && current.value.license.friendly === 'Custom') {
id = id.replaceAll(' ', '-')
}
return id
})
const patchRequestPayload = computed(() => {
const payload: {
license_id?: string
license_url?: string | null // null = remove url
} = {}
if (licenseId.value !== project.value.license.id) {
payload.license_id = licenseId.value
}
if (licenseUrl.value !== project.value.license.url) {
payload.license_url = licenseUrl.value ? licenseUrl.value : null
}
return payload
})
const hasChanges = computed(() => {
return Object.keys(patchRequestPayload.value).length > 0
})
function saveChanges() {
patchProject(patchRequestPayload.value)
}
</script>

View File

@@ -65,7 +65,7 @@
<Checkbox
v-for="category in categoryLists[header]"
:key="`category-${header}-${category.name}`"
:model-value="selectedTags.includes(category)"
:model-value="current.selectedTags.includes(category)"
:description="formatCategory(category.name)"
class="category-selector"
@update:model-value="toggleCategory(category)"
@@ -91,17 +91,17 @@
featured if you do not select all 3.
</span>
</div>
<p v-if="selectedTags.length < 1">
<p v-if="current.selectedTags.length < 1">
Select at least one category in order to feature a category.
</p>
<div class="category-list input-div">
<Checkbox
v-for="category in selectedTags"
v-for="category in current.selectedTags"
:key="`featured-category-${category.name}`"
class="category-selector"
:model-value="featuredTags.includes(category)"
:model-value="current.featuredTags.includes(category)"
:description="formatCategory(category.name)"
:disabled="featuredTags.length >= 3 && !featuredTags.includes(category)"
:disabled="current.featuredTags.length >= 3 && !current.featuredTags.includes(category)"
@update:model-value="toggleFeaturedCategory(category)"
>
<div class="category-selector__label">
@@ -116,32 +116,27 @@
</Checkbox>
</div>
</template>
<div class="button-group">
<button
type="button"
class="iconified-button brand-button"
:disabled="!hasChanges"
@click="saveChanges()"
>
<SaveIcon />
Save changes
</button>
</div>
</section>
<UnsavedChangesPopup
:original="saved"
:modified="current"
:saving="saving"
@reset="reset"
@save="save"
/>
</div>
</template>
<script setup lang="ts">
import { SaveIcon, StarIcon, TriangleAlertIcon } from '@modrinth/assets'
import { Checkbox, injectProjectPageContext } from '@modrinth/ui'
import { StarIcon, TriangleAlertIcon } from '@modrinth/assets'
import { Checkbox, injectProjectPageContext, UnsavedChangesPopup, useSavable } from '@modrinth/ui'
import {
formatCategory,
formatCategoryHeader,
formatProjectType,
sortedCategories,
} from '@modrinth/utils'
import { computed, ref } from 'vue'
import { computed } from 'vue'
interface Category {
name: string
@@ -154,21 +149,56 @@ const tags = useGeneratedState()
const { projectV2: project, patchProject } = injectProjectPageContext()
const selectedTags = ref<Category[]>(
sortedCategories(tags.value).filter(
(x: Category) =>
x.project_type === project.value.actualProjectType &&
(project.value.categories.includes(x.name) ||
project.value.additional_categories.includes(x.name)),
),
)
const { saved, current, saving, reset, save } = useSavable(
() => ({
selectedTags: sortedCategories(tags.value).filter(
(x: Category) =>
x.project_type === project.value.actualProjectType &&
(project.value.categories.includes(x.name) ||
project.value.additional_categories.includes(x.name)),
) as Category[],
featuredTags: sortedCategories(tags.value).filter(
(x: Category) =>
x.project_type === project.value.actualProjectType &&
project.value.categories.includes(x.name),
) as Category[],
}),
async () => {
// Promote selected categories to featured if there are less than 3 featured
const newFeaturedTags = current.value.featuredTags.slice()
if (newFeaturedTags.length < 1 && current.value.selectedTags.length > newFeaturedTags.length) {
const nonFeaturedCategories = current.value.selectedTags.filter(
(x) => !newFeaturedTags.includes(x),
)
nonFeaturedCategories
.slice(0, Math.min(nonFeaturedCategories.length, 3 - newFeaturedTags.length))
.forEach((x) => newFeaturedTags.push(x))
}
const featuredTags = ref<Category[]>(
sortedCategories(tags.value).filter(
(x: Category) =>
x.project_type === project.value.actualProjectType &&
project.value.categories.includes(x.name),
),
// Convert selected and featured categories to backend-usable arrays
const categories = newFeaturedTags.map((x) => x.name)
const additionalCategories = current.value.selectedTags
.filter((x) => !newFeaturedTags.includes(x))
.map((x) => x.name)
const data: Record<string, string[]> = {}
if (
categories.length !== project.value.categories.length ||
categories.some((value) => !project.value.categories.includes(value))
) {
data.categories = categories
}
if (
additionalCategories.length !== project.value.additional_categories.length ||
additionalCategories.some((value) => !project.value.additional_categories.includes(value))
) {
data.additional_categories = additionalCategories
}
await patchProject(data)
},
)
const categoryLists = computed(() => {
@@ -186,7 +216,7 @@ const categoryLists = computed(() => {
})
const tooManyTagsWarning = computed(() => {
const tagCount = selectedTags.value.length
const tagCount = current.value.selectedTags.length
if (tagCount > 8) {
return `You've selected ${tagCount} tags. Consider reducing to 8 or fewer to keep your project focused and easier to discover.`
}
@@ -196,7 +226,7 @@ const tooManyTagsWarning = computed(() => {
const multipleResolutionTagsWarning = computed(() => {
if (project.value.actualProjectType !== 'resourcepack') return null
const resolutionTags = selectedTags.value.filter((tag) =>
const resolutionTags = current.value.selectedTags.filter((tag) =>
['8x-', '16x', '32x', '48x', '64x', '128x', '256x', '512x+'].includes(tag.name),
)
@@ -217,7 +247,7 @@ const allTagsSelectedWarning = computed(() => {
const categoriesForProjectType = sortedCategories(tags.value).filter(
(x: Category) => x.project_type === project.value.actualProjectType,
)
const totalSelectedTags = selectedTags.value.length
const totalSelectedTags = current.value.selectedTags.length
if (
totalSelectedTags === categoriesForProjectType.length &&
@@ -228,68 +258,22 @@ const allTagsSelectedWarning = computed(() => {
return null
})
const patchData = computed(() => {
const data: Record<string, string[]> = {}
// Promote selected categories to featured if there are less than 3 featured
const newFeaturedTags = featuredTags.value.slice()
if (newFeaturedTags.length < 1 && selectedTags.value.length > newFeaturedTags.length) {
const nonFeaturedCategories = selectedTags.value.filter((x) => !newFeaturedTags.includes(x))
nonFeaturedCategories
.slice(0, Math.min(nonFeaturedCategories.length, 3 - newFeaturedTags.length))
.forEach((x) => newFeaturedTags.push(x))
}
// Convert selected and featured categories to backend-usable arrays
const categories = newFeaturedTags.map((x) => x.name)
const additionalCategories = selectedTags.value
.filter((x) => !newFeaturedTags.includes(x))
.map((x) => x.name)
if (
categories.length !== project.value.categories.length ||
categories.some((value) => !project.value.categories.includes(value))
) {
data.categories = categories
}
if (
additionalCategories.length !== project.value.additional_categories.length ||
additionalCategories.some((value) => !project.value.additional_categories.includes(value))
) {
data.additional_categories = additionalCategories
}
return data
})
const hasChanges = computed(() => {
return Object.keys(patchData.value).length > 0
})
const toggleCategory = (category: Category) => {
if (selectedTags.value.includes(category)) {
selectedTags.value = selectedTags.value.filter((x) => x !== category)
if (featuredTags.value.includes(category)) {
featuredTags.value = featuredTags.value.filter((x) => x !== category)
if (current.value.selectedTags.includes(category)) {
current.value.selectedTags = current.value.selectedTags.filter((x) => x !== category)
if (current.value.featuredTags.includes(category)) {
current.value.featuredTags = current.value.featuredTags.filter((x) => x !== category)
}
} else {
selectedTags.value.push(category)
current.value.selectedTags = [...current.value.selectedTags, category]
}
}
const toggleFeaturedCategory = (category: Category) => {
if (featuredTags.value.includes(category)) {
featuredTags.value = featuredTags.value.filter((x) => x !== category)
if (current.value.featuredTags.includes(category)) {
current.value.featuredTags = current.value.featuredTags.filter((x) => x !== category)
} else {
featuredTags.value.push(category)
}
}
const saveChanges = () => {
if (hasChanges.value) {
patchProject(patchData.value)
current.value.featuredTags = [...current.value.featuredTags, category]
}
}
</script>

View File

@@ -1,6 +1,14 @@
<script setup>
import { SaveIcon, TrashIcon, UploadIcon } from '@modrinth/assets'
import { Avatar, Button, ConfirmModal, FileInput, injectNotificationManager } from '@modrinth/ui'
import { TrashIcon, UploadIcon } from '@modrinth/assets'
import {
Avatar,
Button,
ConfirmModal,
FileInput,
injectNotificationManager,
UnsavedChangesPopup,
useSavable,
} from '@modrinth/ui'
import { injectOrganizationContext } from '~/providers/organization-context.ts'
@@ -14,32 +22,49 @@ const {
patchOrganization,
} = injectOrganizationContext()
// Icon state (separate from useSavable, like collection page)
const icon = ref(null)
const deletedIcon = ref(false)
const previewImage = ref(null)
const name = ref(organization.value.name)
const slug = ref(organization.value.slug)
const {
saved,
current,
saving,
hasChanges: hasFieldChanges,
reset: resetFields,
} = useSavable(
() => ({
name: organization.value.name,
slug: organization.value.slug,
summary: organization.value.description,
}),
async ({ name, slug, summary }) => {
await patchOrganization({
...(name !== undefined && { name }),
...(slug !== undefined && { slug }),
...(summary !== undefined && { description: summary }),
})
},
)
const summary = ref(organization.value.description)
// Combined state for UnsavedChangesPopup
const originalState = computed(() => ({
...saved.value,
iconChanged: false,
}))
const patchData = computed(() => {
const data = {}
if (name.value !== organization.value.name) {
data.name = name.value
}
if (slug.value !== organization.value.slug) {
data.slug = slug.value
}
if (summary.value !== organization.value.description) {
data.description = summary.value
}
return data
})
const modifiedState = computed(() => ({
...current.value,
iconChanged: !!(deletedIcon.value || icon.value),
}))
const hasChanges = computed(() => {
return Object.keys(patchData.value).length > 0 || deletedIcon.value || icon.value
})
const reset = () => {
resetFields()
icon.value = null
deletedIcon.value = false
previewImage.value = null
}
const markIconForDeletion = () => {
deletedIcon.value = true
@@ -61,11 +86,16 @@ const showPreviewImage = (files) => {
const orgId = useRouteId()
const onSaveChanges = useClientTry(async () => {
// Only PATCH organization details if there are actual field changes
const hasOrgFieldChanges = Object.keys(patchData.value).length > 0
if (hasOrgFieldChanges) {
await patchOrganization(patchData.value)
const save = async () => {
// Save field changes via useSavable
if (hasFieldChanges.value) {
await patchOrganization({
...(current.value.name !== organization.value.name && { name: current.value.name }),
...(current.value.slug !== organization.value.slug && { slug: current.value.slug }),
...(current.value.summary !== organization.value.description && {
description: current.value.summary,
}),
})
}
// Handle icon deletion / upload separately
@@ -85,7 +115,7 @@ const onSaveChanges = useClientTry(async () => {
text: 'Your organization has been updated.',
type: 'success',
})
})
}
const onDeleteOrganization = useClientTry(async () => {
await useBaseFetch(`organization/${orgId}`, {
@@ -159,7 +189,7 @@ const onDeleteOrganization = useClientTry(async () => {
</label>
<input
id="project-name"
v-model="name"
v-model="current.name"
maxlength="2048"
type="text"
:disabled="!hasPermission"
@@ -172,7 +202,7 @@ const onDeleteOrganization = useClientTry(async () => {
<div class="text-input-wrapper__before">https://modrinth.com/organization/</div>
<input
id="project-slug"
v-model="slug"
v-model="current.slug"
type="text"
maxlength="64"
autocomplete="off"
@@ -186,17 +216,11 @@ const onDeleteOrganization = useClientTry(async () => {
<div class="textarea-wrapper summary-input">
<textarea
id="project-summary"
v-model="summary"
v-model="current.summary"
maxlength="256"
:disabled="!hasPermission"
/>
</div>
<div class="button-group">
<Button color="primary" :disabled="!hasChanges" @click="onSaveChanges">
<SaveIcon />
Save changes
</Button>
</div>
</div>
<div class="universal-card">
<div class="label">
@@ -213,6 +237,13 @@ const onDeleteOrganization = useClientTry(async () => {
Delete organization
</Button>
</div>
<UnsavedChangesPopup
:original="originalState"
:modified="modifiedState"
:saving="saving"
@reset="reset"
@save="save"
/>
</div>
</template>

View File

@@ -56,41 +56,32 @@
{{ formatMessage(messages.usernameDescription) }}
</span>
</label>
<input id="username-field" v-model="username" type="text" />
<input id="username-field" v-model="current.username" type="text" />
<label for="bio-field">
<span class="label__title">{{ formatMessage(messages.bioTitle) }}</span>
<span class="label__description">
{{ formatMessage(messages.bioDescription) }}
</span>
</label>
<textarea id="bio-field" v-model="bio" type="text" />
<div v-if="hasUnsavedChanges" class="input-group">
<Button color="primary" :action="() => saveChanges()">
<SaveIcon /> {{ formatMessage(commonMessages.saveChangesButton) }}
</Button>
<Button :action="() => cancel()">
<XIcon /> {{ formatMessage(commonMessages.cancelButton) }}
</Button>
</div>
<div v-else class="input-group">
<Button disabled color="primary" :action="() => saveChanges()">
<SaveIcon />
{{
saved
? formatMessage(commonMessages.changesSavedLabel)
: formatMessage(commonMessages.saveChangesButton)
}}
</Button>
<textarea id="bio-field" v-model="current.bio" type="text" />
<div class="input-group">
<Button :link="`/user/${auth.user.username}`">
<UserIcon /> {{ formatMessage(commonMessages.visitYourProfile) }}
</Button>
</div>
</section>
<UnsavedChangesPopup
:original="originalState"
:modified="modifiedState"
:saving="saving"
@reset="reset"
@save="save"
/>
</div>
</template>
<script setup>
import { SaveIcon, TrashIcon, UndoIcon, UploadIcon, UserIcon, XIcon } from '@modrinth/assets'
import { TrashIcon, UndoIcon, UploadIcon, UserIcon } from '@modrinth/assets'
import {
Avatar,
Button,
@@ -99,6 +90,8 @@ import {
FileInput,
injectNotificationManager,
IntlFormatted,
UnsavedChangesPopup,
useSavable,
useVIntl,
} from '@modrinth/ui'
@@ -151,21 +144,43 @@ const messages = defineMessages({
const auth = await useAuth()
const username = ref(auth.value.user.username)
const bio = ref(auth.value.user.bio)
// Avatar state (separate from useSavable)
const avatarUrl = ref(auth.value.user.avatar_url)
const icon = shallowRef(null)
const previewImage = shallowRef(null)
const pendingAvatarDeletion = ref(false)
const saved = ref(false)
const saving = ref(false)
const hasUnsavedChanges = computed(
() =>
username.value !== auth.value.user.username ||
bio.value !== auth.value.user.bio ||
previewImage.value,
const {
saved,
current,
reset: resetFields,
} = useSavable(
() => ({
username: auth.value.user.username,
bio: auth.value.user.bio ?? '',
}),
async () => {}, // Save is handled manually due to complex icon logic
)
// Combined state for UnsavedChangesPopup
const originalState = computed(() => ({
...saved.value,
avatarChanged: false,
}))
const modifiedState = computed(() => ({
...current.value,
avatarChanged: !!(previewImage.value || pendingAvatarDeletion.value),
}))
const reset = () => {
resetFields()
icon.value = null
previewImage.value = null
pendingAvatarDeletion.value = false
}
function showPreviewImage(files) {
const reader = new FileReader()
icon.value = files[0]
@@ -180,16 +195,8 @@ function removePreviewImage() {
previewImage.value = 'https://cdn.modrinth.com/placeholder.png'
}
function cancel() {
icon.value = null
previewImage.value = null
pendingAvatarDeletion.value = false
username.value = auth.value.user.username
bio.value = auth.value.user.bio
}
async function saveChanges() {
startLoading()
async function save() {
saving.value = true
try {
if (pendingAvatarDeletion.value) {
await useBaseFetch(`user/${auth.value.user.id}/icon`, {
@@ -215,12 +222,12 @@ async function saveChanges() {
const body = {}
if (auth.value.user.username !== username.value) {
body.username = username.value
if (auth.value.user.username !== current.value.username) {
body.username = current.value.username
}
if (auth.value.user.bio !== bio.value) {
body.bio = bio.value
if (auth.value.user.bio !== current.value.bio) {
body.bio = current.value.bio
}
await useBaseFetch(`user/${auth.value.user.id}`, {
@@ -229,7 +236,6 @@ async function saveChanges() {
})
await useAuth(auth.value.token)
avatarUrl.value = auth.value.user.avatar_url
saved.value = true
} catch (err) {
addNotification({
title: 'An error occurred',
@@ -243,7 +249,7 @@ async function saveChanges() {
type: 'error',
})
}
stopLoading()
saving.value = false
}
</script>
<style lang="scss" scoped>

View File

@@ -53,8 +53,6 @@
},
"dependencies": {
"@codemirror/commands": "^6.3.2",
"intl-messageformat": "^10.7.7",
"vue-i18n": "^10.0.0",
"@codemirror/lang-markdown": "^6.2.3",
"@codemirror/language": "^6.9.3",
"@codemirror/state": "^6.3.2",
@@ -73,13 +71,16 @@
"ace-builds": "^1.43.5",
"apexcharts": "^4.0.0",
"dayjs": "^1.11.10",
"es-toolkit": "^1.44.0",
"floating-vue": "^5.2.2",
"fuse.js": "^6.6.2",
"highlight.js": "^11.9.0",
"intl-messageformat": "^10.7.7",
"markdown-it": "^13.0.2",
"postprocessing": "^6.37.6",
"qrcode.vue": "^3.4.1",
"three": "^0.172.0",
"vue-i18n": "^10.0.0",
"vue-multiselect": "3.0.0",
"vue-select": "4.0.0-beta.6",
"vue-typed-virtual-list": "^1.0.10",

View File

@@ -1,5 +1,6 @@
<script setup lang="ts" generic="T">
import { HistoryIcon, SaveIcon, SpinnerIcon } from '@modrinth/assets'
import { isEqual } from 'es-toolkit'
import { type Component, computed } from 'vue'
import { defineMessage, type MessageDescriptor, useVIntl } from '../../composables/i18n'
@@ -38,15 +39,9 @@ const props = withDefaults(
},
)
const shown = computed(() => {
let changed = false
for (const key of Object.keys(props.modified)) {
if (props.original[key] !== props.modified[key]) {
changed = true
}
}
return changed
})
const shown = computed(() =>
Object.keys(props.modified).some((key) => !isEqual(props.original[key], props.modified[key])),
)
function localizeIfPossible(message: MessageDescriptor | string) {
return typeof message === 'string' ? message : formatMessage(message)

View File

@@ -12,7 +12,7 @@ import {
useSavable,
useVIntl,
} from '@modrinth/ui'
import { computed, ref } from 'vue'
import { computed } from 'vue'
const { formatMessage } = useVIntl()
@@ -20,8 +20,6 @@ const { currentMember, projectV2, projectV3, refreshProject } = injectProjectPag
const { handleError } = injectNotificationManager()
const client = injectModrinthClient()
const saving = ref(false)
const supportsEnvironment = computed(() =>
projectV3.value.project_types.some((type) => ['mod', 'modpack'].includes(type)),
)
@@ -36,26 +34,29 @@ const needsToVerify = computed(
const hasPermission = computed(() => {
const EDIT_DETAILS = 1 << 2
return (currentMember.value?.permissions & EDIT_DETAILS) === EDIT_DETAILS
return ((currentMember.value?.permissions ?? 0) & EDIT_DETAILS) === EDIT_DETAILS
})
function getInitialEnv() {
return projectV3.value.environment?.length === 1 ? projectV3.value.environment[0] : undefined
}
const { saved, current, reset, save } = useSavable(
const { saved, current, saving, reset, save } = useSavable(
() => ({
environment: getInitialEnv(),
side_types_migration_review_status: projectV3.value.side_types_migration_review_status,
}),
({ environment, side_types_migration_review_status }) => {
saving.value = true
side_types_migration_review_status = 'reviewed'
client.labrinth.projects_v3
.edit(projectV2.value.id, { environment, side_types_migration_review_status })
.then(() => refreshProject().then(reset))
.catch(handleError)
.finally(() => (saving.value = false))
async ({ environment }) => {
try {
await client.labrinth.projects_v3.edit(projectV2.value.id, {
environment,
side_types_migration_review_status: 'reviewed',
})
await refreshProject()
reset()
} catch (err) {
handleError(err as Error)
}
},
)
// Set current to reviewed, which will trigger unsaved changes popup.

View File

@@ -31,6 +31,21 @@ export interface ProjectPageContext {
patchProject: (data: Record<string, unknown>, quiet?: boolean) => Promise<boolean>
patchIcon: (icon: File) => Promise<boolean>
setProcessing: () => Promise<void>
createGalleryItem: (
file: File,
title?: string,
description?: string,
featured?: boolean,
ordering?: number,
) => Promise<boolean>
editGalleryItem: (
imageUrl: string,
title?: string,
description?: string,
featured?: boolean,
ordering?: number,
) => Promise<boolean>
deleteGalleryItem: (imageUrl: string) => Promise<boolean>
}
export const [injectProjectPageContext, provideProjectPageContext] =

View File

@@ -1,37 +1,56 @@
import { isEqual } from 'es-toolkit'
import type { ComputedRef, Ref } from 'vue'
import { computed, ref } from 'vue'
export function useSavable<T extends Record<string, unknown>>(
data: () => T,
save: (changes: Partial<T>) => void,
save: (changes: Partial<T>) => void | Promise<void>,
): {
saved: ComputedRef<T>
current: Ref<T>
changes: ComputedRef<Partial<T>>
hasChanges: ComputedRef<boolean>
saving: Ref<boolean>
reset: () => void
save: () => void
save: () => Promise<void>
} {
const savedValues = computed(data)
const currentValues = ref({ ...data() }) as Ref<T>
const saving = ref(false)
const changes = computed<Partial<T>>(() => {
const values: Partial<T> = {}
const keys = Object.keys(currentValues.value) as (keyof T)[]
for (const key of keys) {
if (savedValues.value[key] !== currentValues.value[key]) {
if (!isEqual(savedValues.value[key], currentValues.value[key])) {
values[key] = currentValues.value[key]
}
}
return values
})
const hasChanges = computed(() => Object.keys(changes.value).length > 0)
const reset = () => {
currentValues.value = data()
}
const saveInternal = () => (changes.value ? save(changes.value) : {})
const saveInternal = async () => {
if (!hasChanges.value) return
saving.value = true
try {
await save(changes.value)
} finally {
saving.value = false
}
}
return {
saved: savedValues,
current: currentValues,
changes,
hasChanges,
saving,
reset,
save: saveInternal,
}

140
pnpm-lock.yaml generated
View File

@@ -265,7 +265,7 @@ importers:
version: 0.11.3(magicast@0.5.1)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.26(typescript@5.9.3)))
'@sentry/nuxt':
specifier: ^10.33.0
version: 10.33.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.3.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.3.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.208.0(@opentelemetry/api@1.9.0))(@opentelemetry/resources@2.3.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.3.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.38.0)(magicast@0.5.1)(nuxt@3.20.2(@parcel/watcher@2.5.1)(@types/node@20.19.27)(@vue/compiler-sfc@3.5.26)(cac@6.7.14)(db0@0.3.4)(eslint@9.39.2(jiti@1.21.7))(ioredis@5.8.2)(lightningcss@1.30.2)(magicast@0.5.1)(optionator@0.9.4)(rollup@4.54.0)(sass@1.97.1)(terser@5.44.1)(typescript@5.9.3)(vite@7.3.0(@types/node@20.19.27)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2))(vue-tsc@2.2.12(typescript@5.9.3))(xml2js@0.6.2)(yaml@2.8.2))(pinia@3.0.4(typescript@5.9.3)(vue@3.5.26(typescript@5.9.3)))(rollup@4.54.0)(vue@3.5.26(typescript@5.9.3))
version: 10.33.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.3.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.3.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.208.0(@opentelemetry/api@1.9.0))(@opentelemetry/resources@2.3.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.3.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.38.0)(magicast@0.5.1)(nuxt@3.20.2(@parcel/watcher@2.5.1)(@types/node@20.19.27)(@vue/compiler-sfc@3.5.26)(cac@6.7.14)(db0@0.3.4)(eslint@9.39.2(jiti@2.6.1))(ioredis@5.8.2)(lightningcss@1.30.2)(magicast@0.5.1)(optionator@0.9.4)(rollup@4.54.0)(sass@1.97.1)(terser@5.44.1)(typescript@5.9.3)(vite@7.3.0(@types/node@20.19.27)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2))(vue-tsc@2.2.12(typescript@5.9.3))(xml2js@0.6.2)(yaml@2.8.2))(pinia@3.0.4(typescript@5.9.3)(vue@3.5.26(typescript@5.9.3)))(rollup@4.54.0)(vue@3.5.26(typescript@5.9.3))
'@tanstack/vue-query':
specifier: ^5.90.7
version: 5.92.5(vue@3.5.26(typescript@5.9.3))
@@ -392,7 +392,7 @@ importers:
version: 10.5.0
nuxt:
specifier: ^3.20.2
version: 3.20.2(@parcel/watcher@2.5.1)(@types/node@20.19.27)(@vue/compiler-sfc@3.5.26)(cac@6.7.14)(db0@0.3.4)(eslint@9.39.2(jiti@1.21.7))(ioredis@5.8.2)(lightningcss@1.30.2)(magicast@0.5.1)(optionator@0.9.4)(rollup@4.54.0)(sass@1.97.1)(terser@5.44.1)(typescript@5.9.3)(vite@7.3.0(@types/node@20.19.27)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2))(vue-tsc@2.2.12(typescript@5.9.3))(xml2js@0.6.2)(yaml@2.8.2)
version: 3.20.2(@parcel/watcher@2.5.1)(@types/node@20.19.27)(@vue/compiler-sfc@3.5.26)(cac@6.7.14)(db0@0.3.4)(eslint@9.39.2(jiti@2.6.1))(ioredis@5.8.2)(lightningcss@1.30.2)(magicast@0.5.1)(optionator@0.9.4)(rollup@4.54.0)(sass@1.97.1)(terser@5.44.1)(typescript@5.9.3)(vite@7.3.0(@types/node@20.19.27)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2))(vue-tsc@2.2.12(typescript@5.9.3))(xml2js@0.6.2)(yaml@2.8.2)
postcss:
specifier: ^8.4.39
version: 8.5.6
@@ -634,6 +634,9 @@ importers:
dayjs:
specifier: ^1.11.10
version: 1.11.19
es-toolkit:
specifier: ^1.44.0
version: 1.44.0
floating-vue:
specifier: ^5.2.2
version: 5.2.2(@nuxt/kit@3.20.2(magicast@0.5.1))(vue@3.5.26(typescript@5.9.3))
@@ -724,7 +727,7 @@ importers:
version: 4.0.16(@vitest/browser@4.0.16(vite@5.4.21(@types/node@20.19.27)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1))(vitest@4.0.16))(vitest@4.0.16)
eslint-plugin-storybook:
specifier: ^10.1.10
version: 10.1.11(eslint@9.39.2(jiti@2.6.1))(storybook@10.1.11(@testing-library/dom@10.4.1)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(typescript@5.9.3)
version: 10.1.11(eslint@9.39.2(jiti@1.21.7))(storybook@10.1.11(@testing-library/dom@10.4.1)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(typescript@5.9.3)
playwright:
specifier: ^1.57.0
version: 1.57.0
@@ -748,7 +751,7 @@ importers:
version: 5.1.0(vue@3.5.26(typescript@5.9.3))
vitest:
specifier: ^4.0.16
version: 4.0.16(@opentelemetry/api@1.9.0)(@types/node@20.19.27)(@vitest/browser-playwright@4.0.16)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2)
version: 4.0.16(@opentelemetry/api@1.9.0)(@types/node@20.19.27)(@vitest/browser-playwright@4.0.16)(jiti@1.21.7)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2)
vue:
specifier: ^3.5.13
version: 3.5.26(typescript@5.9.3)
@@ -5433,6 +5436,9 @@ packages:
resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
engines: {node: '>= 0.4'}
es-toolkit@1.44.0:
resolution: {integrity: sha512-6penXeZalaV88MM3cGkFZZfOoLGWshWWfdy0tWw/RlVVyhvMaWSBTOvXNeiW3e5FwdS5ePW0LGEu17zT139ktg==}
esast-util-from-estree@2.0.0:
resolution: {integrity: sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==}
@@ -8485,6 +8491,7 @@ packages:
tar@7.5.2:
resolution: {integrity: sha512-7NyxrTE4Anh8km8iEy7o0QYPs+0JKBTj5ZaqHg6B39erLg0qYXN3BijtShwbsNSvQ+LN75+KV+C4QR/f6Gwnpg==}
engines: {node: '>=18'}
deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exhorbitant rates) by contacting i@izs.me
terser@5.44.1:
resolution: {integrity: sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==}
@@ -9245,8 +9252,8 @@ packages:
vue-component-type-helpers@3.2.1:
resolution: {integrity: sha512-gKV7XOkQl4urSuLHNY1tnVQf7wVgtb/mKbRyxSLWGZUY9RK7aDPhBenTjm+i8ZFe0zC2PZeHMPtOZXZfyaFOzQ==}
vue-component-type-helpers@3.2.2:
resolution: {integrity: sha512-x8C2nx5XlUNM0WirgfTkHjJGO/ABBxlANZDtHw2HclHtQnn+RFPTnbjMJn8jHZW4TlUam0asHcA14lf1C6Jb+A==}
vue-component-type-helpers@3.2.4:
resolution: {integrity: sha512-05lR16HeZDcDpB23ku5b5f1fBOoHqFnMiKRr2CiEvbG5Ux4Yi0McmQBOET0dR0nxDXosxyVqv67q6CzS3AK8rw==}
vue-confetti-explosion@1.0.2:
resolution: {integrity: sha512-80OboM3/6BItIoZ6DpNcZFqGpF607kjIVc5af56oKgtFmt5yWehvJeoYhkzYlqxrqdBe0Ko4Ie3bWrmLau+dJw==}
@@ -10415,7 +10422,6 @@ snapshots:
dependencies:
eslint: 9.39.2(jiti@1.21.7)
eslint-visitor-keys: 3.4.3
optional: true
'@eslint-community/eslint-utils@4.9.1(eslint@9.39.2(jiti@2.6.1))':
dependencies:
@@ -11000,7 +11006,7 @@ snapshots:
dependencies:
'@nuxt/kit': 4.2.2(magicast@0.5.1)
execa: 8.0.1
vite: 7.3.0(@types/node@20.19.27)(jiti@1.21.7)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2)
vite: 7.3.0(@types/node@20.19.27)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2)
transitivePeerDependencies:
- magicast
@@ -11045,7 +11051,7 @@ snapshots:
sirv: 3.0.2
structured-clone-es: 1.0.0
tinyglobby: 0.2.15
vite: 7.3.0(@types/node@20.19.27)(jiti@1.21.7)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2)
vite: 7.3.0(@types/node@20.19.27)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2)
vite-plugin-inspect: 11.3.3(@nuxt/kit@4.2.2(magicast@0.5.1))(vite@7.3.0(@types/node@20.19.27)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2))
vite-plugin-vue-tracer: 1.2.0(vite@7.3.0(@types/node@20.19.27)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2))(vue@3.5.26(typescript@5.9.3))
which: 5.0.0
@@ -11141,7 +11147,7 @@ snapshots:
transitivePeerDependencies:
- magicast
'@nuxt/nitro-server@3.20.2(db0@0.3.4)(ioredis@5.8.2)(magicast@0.5.1)(nuxt@3.20.2(@parcel/watcher@2.5.1)(@types/node@20.19.27)(@vue/compiler-sfc@3.5.26)(cac@6.7.14)(db0@0.3.4)(eslint@9.39.2(jiti@1.21.7))(ioredis@5.8.2)(lightningcss@1.30.2)(magicast@0.5.1)(optionator@0.9.4)(rollup@4.54.0)(sass@1.97.1)(terser@5.44.1)(typescript@5.9.3)(vite@7.3.0(@types/node@20.19.27)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2))(vue-tsc@2.2.12(typescript@5.9.3))(xml2js@0.6.2)(yaml@2.8.2))(typescript@5.9.3)(xml2js@0.6.2)':
'@nuxt/nitro-server@3.20.2(db0@0.3.4)(ioredis@5.8.2)(magicast@0.5.1)(nuxt@3.20.2(@parcel/watcher@2.5.1)(@types/node@20.19.27)(@vue/compiler-sfc@3.5.26)(cac@6.7.14)(db0@0.3.4)(eslint@9.39.2(jiti@2.6.1))(ioredis@5.8.2)(lightningcss@1.30.2)(magicast@0.5.1)(optionator@0.9.4)(rollup@4.54.0)(sass@1.97.1)(terser@5.44.1)(typescript@5.9.3)(vite@7.3.0(@types/node@20.19.27)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2))(vue-tsc@2.2.12(typescript@5.9.3))(xml2js@0.6.2)(yaml@2.8.2))(typescript@5.9.3)(xml2js@0.6.2)':
dependencies:
'@nuxt/devalue': 2.0.2
'@nuxt/kit': 3.20.2(magicast@0.5.1)
@@ -11159,7 +11165,7 @@ snapshots:
klona: 2.0.6
mocked-exports: 0.1.1
nitropack: 2.12.9(xml2js@0.6.2)
nuxt: 3.20.2(@parcel/watcher@2.5.1)(@types/node@20.19.27)(@vue/compiler-sfc@3.5.26)(cac@6.7.14)(db0@0.3.4)(eslint@9.39.2(jiti@1.21.7))(ioredis@5.8.2)(lightningcss@1.30.2)(magicast@0.5.1)(optionator@0.9.4)(rollup@4.54.0)(sass@1.97.1)(terser@5.44.1)(typescript@5.9.3)(vite@7.3.0(@types/node@20.19.27)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2))(vue-tsc@2.2.12(typescript@5.9.3))(xml2js@0.6.2)(yaml@2.8.2)
nuxt: 3.20.2(@parcel/watcher@2.5.1)(@types/node@20.19.27)(@vue/compiler-sfc@3.5.26)(cac@6.7.14)(db0@0.3.4)(eslint@9.39.2(jiti@2.6.1))(ioredis@5.8.2)(lightningcss@1.30.2)(magicast@0.5.1)(optionator@0.9.4)(rollup@4.54.0)(sass@1.97.1)(terser@5.44.1)(typescript@5.9.3)(vite@7.3.0(@types/node@20.19.27)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2))(vue-tsc@2.2.12(typescript@5.9.3))(xml2js@0.6.2)(yaml@2.8.2)
pathe: 2.0.3
pkg-types: 2.3.0
radix3: 1.1.2
@@ -11230,7 +11236,7 @@ snapshots:
transitivePeerDependencies:
- magicast
'@nuxt/vite-builder@3.20.2(@types/node@20.19.27)(eslint@9.39.2(jiti@1.21.7))(lightningcss@1.30.2)(magicast@0.5.1)(nuxt@3.20.2(@parcel/watcher@2.5.1)(@types/node@20.19.27)(@vue/compiler-sfc@3.5.26)(cac@6.7.14)(db0@0.3.4)(eslint@9.39.2(jiti@1.21.7))(ioredis@5.8.2)(lightningcss@1.30.2)(magicast@0.5.1)(optionator@0.9.4)(rollup@4.54.0)(sass@1.97.1)(terser@5.44.1)(typescript@5.9.3)(vite@7.3.0(@types/node@20.19.27)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2))(vue-tsc@2.2.12(typescript@5.9.3))(xml2js@0.6.2)(yaml@2.8.2))(optionator@0.9.4)(rollup@4.54.0)(sass@1.97.1)(terser@5.44.1)(typescript@5.9.3)(vue-tsc@2.2.12(typescript@5.9.3))(vue@3.5.26(typescript@5.9.3))(yaml@2.8.2)':
'@nuxt/vite-builder@3.20.2(@types/node@20.19.27)(eslint@9.39.2(jiti@2.6.1))(lightningcss@1.30.2)(magicast@0.5.1)(nuxt@3.20.2(@parcel/watcher@2.5.1)(@types/node@20.19.27)(@vue/compiler-sfc@3.5.26)(cac@6.7.14)(db0@0.3.4)(eslint@9.39.2(jiti@2.6.1))(ioredis@5.8.2)(lightningcss@1.30.2)(magicast@0.5.1)(optionator@0.9.4)(rollup@4.54.0)(sass@1.97.1)(terser@5.44.1)(typescript@5.9.3)(vite@7.3.0(@types/node@20.19.27)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2))(vue-tsc@2.2.12(typescript@5.9.3))(xml2js@0.6.2)(yaml@2.8.2))(optionator@0.9.4)(rollup@4.54.0)(sass@1.97.1)(terser@5.44.1)(typescript@5.9.3)(vue-tsc@2.2.12(typescript@5.9.3))(vue@3.5.26(typescript@5.9.3))(yaml@2.8.2)':
dependencies:
'@nuxt/kit': 3.20.2(magicast@0.5.1)
'@rollup/plugin-replace': 6.0.3(rollup@4.54.0)
@@ -11251,7 +11257,7 @@ snapshots:
magic-string: 0.30.21
mlly: 1.8.0
mocked-exports: 0.1.1
nuxt: 3.20.2(@parcel/watcher@2.5.1)(@types/node@20.19.27)(@vue/compiler-sfc@3.5.26)(cac@6.7.14)(db0@0.3.4)(eslint@9.39.2(jiti@1.21.7))(ioredis@5.8.2)(lightningcss@1.30.2)(magicast@0.5.1)(optionator@0.9.4)(rollup@4.54.0)(sass@1.97.1)(terser@5.44.1)(typescript@5.9.3)(vite@7.3.0(@types/node@20.19.27)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2))(vue-tsc@2.2.12(typescript@5.9.3))(xml2js@0.6.2)(yaml@2.8.2)
nuxt: 3.20.2(@parcel/watcher@2.5.1)(@types/node@20.19.27)(@vue/compiler-sfc@3.5.26)(cac@6.7.14)(db0@0.3.4)(eslint@9.39.2(jiti@2.6.1))(ioredis@5.8.2)(lightningcss@1.30.2)(magicast@0.5.1)(optionator@0.9.4)(rollup@4.54.0)(sass@1.97.1)(terser@5.44.1)(typescript@5.9.3)(vite@7.3.0(@types/node@20.19.27)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2))(vue-tsc@2.2.12(typescript@5.9.3))(xml2js@0.6.2)(yaml@2.8.2)
ohash: 2.0.11
pathe: 2.0.3
perfect-debounce: 2.0.0
@@ -11262,9 +11268,9 @@ snapshots:
std-env: 3.10.0
ufo: 1.6.1
unenv: 2.0.0-rc.24
vite: 7.3.0(@types/node@20.19.27)(jiti@1.21.7)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2)
vite: 7.3.0(@types/node@20.19.27)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2)
vite-node: 5.2.0(@types/node@20.19.27)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2)
vite-plugin-checker: 0.12.0(eslint@9.39.2(jiti@1.21.7))(optionator@0.9.4)(typescript@5.9.3)(vite@7.3.0(@types/node@20.19.27)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2))(vue-tsc@2.2.12(typescript@5.9.3))
vite-plugin-checker: 0.12.0(eslint@9.39.2(jiti@2.6.1))(optionator@0.9.4)(typescript@5.9.3)(vite@7.3.0(@types/node@20.19.27)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2))(vue-tsc@2.2.12(typescript@5.9.3))
vue: 3.5.26(typescript@5.9.3)
vue-bundle-renderer: 2.2.0
transitivePeerDependencies:
@@ -12148,7 +12154,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@sentry/nuxt@10.33.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.3.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.3.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.208.0(@opentelemetry/api@1.9.0))(@opentelemetry/resources@2.3.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.3.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.38.0)(magicast@0.5.1)(nuxt@3.20.2(@parcel/watcher@2.5.1)(@types/node@20.19.27)(@vue/compiler-sfc@3.5.26)(cac@6.7.14)(db0@0.3.4)(eslint@9.39.2(jiti@1.21.7))(ioredis@5.8.2)(lightningcss@1.30.2)(magicast@0.5.1)(optionator@0.9.4)(rollup@4.54.0)(sass@1.97.1)(terser@5.44.1)(typescript@5.9.3)(vite@7.3.0(@types/node@20.19.27)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2))(vue-tsc@2.2.12(typescript@5.9.3))(xml2js@0.6.2)(yaml@2.8.2))(pinia@3.0.4(typescript@5.9.3)(vue@3.5.26(typescript@5.9.3)))(rollup@4.54.0)(vue@3.5.26(typescript@5.9.3))':
'@sentry/nuxt@10.33.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.3.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.3.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.208.0(@opentelemetry/api@1.9.0))(@opentelemetry/resources@2.3.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.3.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.38.0)(magicast@0.5.1)(nuxt@3.20.2(@parcel/watcher@2.5.1)(@types/node@20.19.27)(@vue/compiler-sfc@3.5.26)(cac@6.7.14)(db0@0.3.4)(eslint@9.39.2(jiti@2.6.1))(ioredis@5.8.2)(lightningcss@1.30.2)(magicast@0.5.1)(optionator@0.9.4)(rollup@4.54.0)(sass@1.97.1)(terser@5.44.1)(typescript@5.9.3)(vite@7.3.0(@types/node@20.19.27)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2))(vue-tsc@2.2.12(typescript@5.9.3))(xml2js@0.6.2)(yaml@2.8.2))(pinia@3.0.4(typescript@5.9.3)(vue@3.5.26(typescript@5.9.3)))(rollup@4.54.0)(vue@3.5.26(typescript@5.9.3))':
dependencies:
'@nuxt/kit': 3.20.2(magicast@0.5.1)
'@sentry/browser': 10.33.0
@@ -12159,7 +12165,7 @@ snapshots:
'@sentry/rollup-plugin': 4.6.1(rollup@4.54.0)
'@sentry/vite-plugin': 4.6.1
'@sentry/vue': 10.33.0(pinia@3.0.4(typescript@5.9.3)(vue@3.5.26(typescript@5.9.3)))(vue@3.5.26(typescript@5.9.3))
nuxt: 3.20.2(@parcel/watcher@2.5.1)(@types/node@20.19.27)(@vue/compiler-sfc@3.5.26)(cac@6.7.14)(db0@0.3.4)(eslint@9.39.2(jiti@1.21.7))(ioredis@5.8.2)(lightningcss@1.30.2)(magicast@0.5.1)(optionator@0.9.4)(rollup@4.54.0)(sass@1.97.1)(terser@5.44.1)(typescript@5.9.3)(vite@7.3.0(@types/node@20.19.27)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2))(vue-tsc@2.2.12(typescript@5.9.3))(xml2js@0.6.2)(yaml@2.8.2)
nuxt: 3.20.2(@parcel/watcher@2.5.1)(@types/node@20.19.27)(@vue/compiler-sfc@3.5.26)(cac@6.7.14)(db0@0.3.4)(eslint@9.39.2(jiti@2.6.1))(ioredis@5.8.2)(lightningcss@1.30.2)(magicast@0.5.1)(optionator@0.9.4)(rollup@4.54.0)(sass@1.97.1)(terser@5.44.1)(typescript@5.9.3)(vite@7.3.0(@types/node@20.19.27)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2))(vue-tsc@2.2.12(typescript@5.9.3))(xml2js@0.6.2)(yaml@2.8.2)
transitivePeerDependencies:
- '@cloudflare/workers-types'
- '@opentelemetry/api'
@@ -12336,7 +12342,7 @@ snapshots:
'@vitest/browser': 4.0.16(vite@5.4.21(@types/node@20.19.27)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1))(vitest@4.0.16)
'@vitest/browser-playwright': 4.0.16(playwright@1.57.0)(vite@5.4.21(@types/node@20.19.27)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1))(vitest@4.0.16)
'@vitest/runner': 4.0.16
vitest: 4.0.16(@opentelemetry/api@1.9.0)(@types/node@20.19.27)(@vitest/browser-playwright@4.0.16)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2)
vitest: 4.0.16(@opentelemetry/api@1.9.0)(@types/node@20.19.27)(@vitest/browser-playwright@4.0.16)(jiti@1.21.7)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2)
transitivePeerDependencies:
- react
- react-dom
@@ -12399,7 +12405,7 @@ snapshots:
storybook: 10.1.11(@testing-library/dom@10.4.1)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
type-fest: 2.19.0
vue: 3.5.26(typescript@5.9.3)
vue-component-type-helpers: 3.2.2
vue-component-type-helpers: 3.2.4
'@stripe/stripe-js@7.9.0': {}
@@ -12894,6 +12900,17 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@typescript-eslint/utils@8.51.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)':
dependencies:
'@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@1.21.7))
'@typescript-eslint/scope-manager': 8.51.0
'@typescript-eslint/types': 8.51.0
'@typescript-eslint/typescript-estree': 8.51.0(typescript@5.9.3)
eslint: 9.39.2(jiti@1.21.7)
typescript: 5.9.3
transitivePeerDependencies:
- supports-color
'@typescript-eslint/utils@8.51.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)':
dependencies:
'@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@2.6.1))
@@ -13008,7 +13025,7 @@ snapshots:
'@babel/plugin-transform-typescript': 7.28.5(@babel/core@7.28.5)
'@rolldown/pluginutils': 1.0.0-beta.58
'@vue/babel-plugin-jsx': 2.0.1(@babel/core@7.28.5)
vite: 7.3.0(@types/node@20.19.27)(jiti@1.21.7)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2)
vite: 7.3.0(@types/node@20.19.27)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2)
vue: 3.5.26(typescript@5.9.3)
transitivePeerDependencies:
- supports-color
@@ -13027,7 +13044,7 @@ snapshots:
'@vitejs/plugin-vue@6.0.3(vite@7.3.0(@types/node@20.19.27)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2))(vue@3.5.26(typescript@5.9.3))':
dependencies:
'@rolldown/pluginutils': 1.0.0-beta.53
vite: 7.3.0(@types/node@20.19.27)(jiti@1.21.7)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2)
vite: 7.3.0(@types/node@20.19.27)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2)
vue: 3.5.26(typescript@5.9.3)
'@vitest/browser-playwright@4.0.16(playwright@1.57.0)(vite@5.4.21(@types/node@20.19.27)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1))(vitest@4.0.16)':
@@ -13036,7 +13053,7 @@ snapshots:
'@vitest/mocker': 4.0.16(vite@5.4.21(@types/node@20.19.27)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1))
playwright: 1.57.0
tinyrainbow: 3.0.3
vitest: 4.0.16(@opentelemetry/api@1.9.0)(@types/node@20.19.27)(@vitest/browser-playwright@4.0.16)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2)
vitest: 4.0.16(@opentelemetry/api@1.9.0)(@types/node@20.19.27)(@vitest/browser-playwright@4.0.16)(jiti@1.21.7)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2)
transitivePeerDependencies:
- bufferutil
- msw
@@ -13052,7 +13069,7 @@ snapshots:
pngjs: 7.0.0
sirv: 3.0.2
tinyrainbow: 3.0.3
vitest: 4.0.16(@opentelemetry/api@1.9.0)(@types/node@20.19.27)(@vitest/browser-playwright@4.0.16)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2)
vitest: 4.0.16(@opentelemetry/api@1.9.0)(@types/node@20.19.27)(@vitest/browser-playwright@4.0.16)(jiti@1.21.7)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2)
ws: 8.18.3
transitivePeerDependencies:
- bufferutil
@@ -13073,7 +13090,7 @@ snapshots:
obug: 2.1.1
std-env: 3.10.0
tinyrainbow: 3.0.3
vitest: 4.0.16(@opentelemetry/api@1.9.0)(@types/node@20.19.27)(@vitest/browser-playwright@4.0.16)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2)
vitest: 4.0.16(@opentelemetry/api@1.9.0)(@types/node@20.19.27)(@vitest/browser-playwright@4.0.16)(jiti@1.21.7)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2)
optionalDependencies:
'@vitest/browser': 4.0.16(vite@5.4.21(@types/node@20.19.27)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1))(vitest@4.0.16)
transitivePeerDependencies:
@@ -13112,13 +13129,13 @@ snapshots:
optionalDependencies:
vite: 5.4.21(@types/node@20.19.27)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)
'@vitest/mocker@4.0.16(vite@6.4.1(@types/node@20.19.27)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2))':
'@vitest/mocker@4.0.16(vite@6.4.1(@types/node@20.19.27)(jiti@1.21.7)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2))':
dependencies:
'@vitest/spy': 4.0.16
estree-walker: 3.0.3
magic-string: 0.30.21
optionalDependencies:
vite: 6.4.1(@types/node@20.19.27)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2)
vite: 6.4.1(@types/node@20.19.27)(jiti@1.21.7)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2)
'@vitest/pretty-format@3.2.4':
dependencies:
@@ -14495,6 +14512,8 @@ snapshots:
dependencies:
es-errors: 1.3.0
es-toolkit@1.44.0: {}
esast-util-from-estree@2.0.0:
dependencies:
'@types/estree-jsx': 1.0.5
@@ -14710,10 +14729,10 @@ snapshots:
dependencies:
eslint: 9.39.2(jiti@2.6.1)
eslint-plugin-storybook@10.1.11(eslint@9.39.2(jiti@2.6.1))(storybook@10.1.11(@testing-library/dom@10.4.1)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(typescript@5.9.3):
eslint-plugin-storybook@10.1.11(eslint@9.39.2(jiti@1.21.7))(storybook@10.1.11(@testing-library/dom@10.4.1)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(typescript@5.9.3):
dependencies:
'@typescript-eslint/utils': 8.51.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
eslint: 9.39.2(jiti@2.6.1)
'@typescript-eslint/utils': 8.51.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)
eslint: 9.39.2(jiti@1.21.7)
storybook: 10.1.11(@testing-library/dom@10.4.1)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
transitivePeerDependencies:
- supports-color
@@ -14827,7 +14846,6 @@ snapshots:
jiti: 1.21.7
transitivePeerDependencies:
- supports-color
optional: true
eslint@9.39.2(jiti@2.6.1):
dependencies:
@@ -16790,16 +16808,16 @@ snapshots:
dependencies:
boolbase: 1.0.0
nuxt@3.20.2(@parcel/watcher@2.5.1)(@types/node@20.19.27)(@vue/compiler-sfc@3.5.26)(cac@6.7.14)(db0@0.3.4)(eslint@9.39.2(jiti@1.21.7))(ioredis@5.8.2)(lightningcss@1.30.2)(magicast@0.5.1)(optionator@0.9.4)(rollup@4.54.0)(sass@1.97.1)(terser@5.44.1)(typescript@5.9.3)(vite@7.3.0(@types/node@20.19.27)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2))(vue-tsc@2.2.12(typescript@5.9.3))(xml2js@0.6.2)(yaml@2.8.2):
nuxt@3.20.2(@parcel/watcher@2.5.1)(@types/node@20.19.27)(@vue/compiler-sfc@3.5.26)(cac@6.7.14)(db0@0.3.4)(eslint@9.39.2(jiti@2.6.1))(ioredis@5.8.2)(lightningcss@1.30.2)(magicast@0.5.1)(optionator@0.9.4)(rollup@4.54.0)(sass@1.97.1)(terser@5.44.1)(typescript@5.9.3)(vite@7.3.0(@types/node@20.19.27)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2))(vue-tsc@2.2.12(typescript@5.9.3))(xml2js@0.6.2)(yaml@2.8.2):
dependencies:
'@dxup/nuxt': 0.2.2(magicast@0.5.1)
'@nuxt/cli': 3.31.3(cac@6.7.14)(magicast@0.5.1)
'@nuxt/devtools': 3.1.1(vite@7.3.0(@types/node@20.19.27)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2))(vue@3.5.26(typescript@5.9.3))
'@nuxt/kit': 3.20.2(magicast@0.5.1)
'@nuxt/nitro-server': 3.20.2(db0@0.3.4)(ioredis@5.8.2)(magicast@0.5.1)(nuxt@3.20.2(@parcel/watcher@2.5.1)(@types/node@20.19.27)(@vue/compiler-sfc@3.5.26)(cac@6.7.14)(db0@0.3.4)(eslint@9.39.2(jiti@1.21.7))(ioredis@5.8.2)(lightningcss@1.30.2)(magicast@0.5.1)(optionator@0.9.4)(rollup@4.54.0)(sass@1.97.1)(terser@5.44.1)(typescript@5.9.3)(vite@7.3.0(@types/node@20.19.27)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2))(vue-tsc@2.2.12(typescript@5.9.3))(xml2js@0.6.2)(yaml@2.8.2))(typescript@5.9.3)(xml2js@0.6.2)
'@nuxt/nitro-server': 3.20.2(db0@0.3.4)(ioredis@5.8.2)(magicast@0.5.1)(nuxt@3.20.2(@parcel/watcher@2.5.1)(@types/node@20.19.27)(@vue/compiler-sfc@3.5.26)(cac@6.7.14)(db0@0.3.4)(eslint@9.39.2(jiti@2.6.1))(ioredis@5.8.2)(lightningcss@1.30.2)(magicast@0.5.1)(optionator@0.9.4)(rollup@4.54.0)(sass@1.97.1)(terser@5.44.1)(typescript@5.9.3)(vite@7.3.0(@types/node@20.19.27)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2))(vue-tsc@2.2.12(typescript@5.9.3))(xml2js@0.6.2)(yaml@2.8.2))(typescript@5.9.3)(xml2js@0.6.2)
'@nuxt/schema': 3.20.2
'@nuxt/telemetry': 2.6.6(magicast@0.5.1)
'@nuxt/vite-builder': 3.20.2(@types/node@20.19.27)(eslint@9.39.2(jiti@1.21.7))(lightningcss@1.30.2)(magicast@0.5.1)(nuxt@3.20.2(@parcel/watcher@2.5.1)(@types/node@20.19.27)(@vue/compiler-sfc@3.5.26)(cac@6.7.14)(db0@0.3.4)(eslint@9.39.2(jiti@1.21.7))(ioredis@5.8.2)(lightningcss@1.30.2)(magicast@0.5.1)(optionator@0.9.4)(rollup@4.54.0)(sass@1.97.1)(terser@5.44.1)(typescript@5.9.3)(vite@7.3.0(@types/node@20.19.27)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2))(vue-tsc@2.2.12(typescript@5.9.3))(xml2js@0.6.2)(yaml@2.8.2))(optionator@0.9.4)(rollup@4.54.0)(sass@1.97.1)(terser@5.44.1)(typescript@5.9.3)(vue-tsc@2.2.12(typescript@5.9.3))(vue@3.5.26(typescript@5.9.3))(yaml@2.8.2)
'@nuxt/vite-builder': 3.20.2(@types/node@20.19.27)(eslint@9.39.2(jiti@2.6.1))(lightningcss@1.30.2)(magicast@0.5.1)(nuxt@3.20.2(@parcel/watcher@2.5.1)(@types/node@20.19.27)(@vue/compiler-sfc@3.5.26)(cac@6.7.14)(db0@0.3.4)(eslint@9.39.2(jiti@2.6.1))(ioredis@5.8.2)(lightningcss@1.30.2)(magicast@0.5.1)(optionator@0.9.4)(rollup@4.54.0)(sass@1.97.1)(terser@5.44.1)(typescript@5.9.3)(vite@7.3.0(@types/node@20.19.27)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2))(vue-tsc@2.2.12(typescript@5.9.3))(xml2js@0.6.2)(yaml@2.8.2))(optionator@0.9.4)(rollup@4.54.0)(sass@1.97.1)(terser@5.44.1)(typescript@5.9.3)(vue-tsc@2.2.12(typescript@5.9.3))(vue@3.5.26(typescript@5.9.3))(yaml@2.8.2)
'@unhead/vue': 2.1.1(vue@3.5.26(typescript@5.9.3))
'@vue/shared': 3.5.26
c12: 3.3.3(magicast@0.5.1)
@@ -18959,12 +18977,12 @@ snapshots:
vite-dev-rpc@1.1.0(vite@7.3.0(@types/node@20.19.27)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2)):
dependencies:
birpc: 2.9.0
vite: 7.3.0(@types/node@20.19.27)(jiti@1.21.7)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2)
vite: 7.3.0(@types/node@20.19.27)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2)
vite-hot-client: 2.1.0(vite@7.3.0(@types/node@20.19.27)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2))
vite-hot-client@2.1.0(vite@7.3.0(@types/node@20.19.27)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2)):
dependencies:
vite: 7.3.0(@types/node@20.19.27)(jiti@1.21.7)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2)
vite: 7.3.0(@types/node@20.19.27)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2)
vite-node@5.2.0(@types/node@20.19.27)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2):
dependencies:
@@ -18986,7 +19004,7 @@ snapshots:
- tsx
- yaml
vite-plugin-checker@0.12.0(eslint@9.39.2(jiti@1.21.7))(optionator@0.9.4)(typescript@5.9.3)(vite@7.3.0(@types/node@20.19.27)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2))(vue-tsc@2.2.12(typescript@5.9.3)):
vite-plugin-checker@0.12.0(eslint@9.39.2(jiti@2.6.1))(optionator@0.9.4)(typescript@5.9.3)(vite@7.3.0(@types/node@20.19.27)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2))(vue-tsc@2.2.12(typescript@5.9.3)):
dependencies:
'@babel/code-frame': 7.27.1
chokidar: 4.0.3
@@ -18995,10 +19013,10 @@ snapshots:
picomatch: 4.0.3
tiny-invariant: 1.3.3
tinyglobby: 0.2.15
vite: 7.3.0(@types/node@20.19.27)(jiti@1.21.7)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2)
vite: 7.3.0(@types/node@20.19.27)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2)
vscode-uri: 3.1.0
optionalDependencies:
eslint: 9.39.2(jiti@1.21.7)
eslint: 9.39.2(jiti@2.6.1)
optionator: 0.9.4
typescript: 5.9.3
vue-tsc: 2.2.12(typescript@5.9.3)
@@ -19013,7 +19031,7 @@ snapshots:
perfect-debounce: 2.0.0
sirv: 3.0.2
unplugin-utils: 0.3.1
vite: 7.3.0(@types/node@20.19.27)(jiti@1.21.7)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2)
vite: 7.3.0(@types/node@20.19.27)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2)
vite-dev-rpc: 1.1.0(vite@7.3.0(@types/node@20.19.27)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2))
optionalDependencies:
'@nuxt/kit': 4.2.2(magicast@0.5.1)
@@ -19027,7 +19045,7 @@ snapshots:
magic-string: 0.30.21
pathe: 2.0.3
source-map-js: 1.2.1
vite: 7.3.0(@types/node@20.19.27)(jiti@1.21.7)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2)
vite: 7.3.0(@types/node@20.19.27)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2)
vue: 3.5.26(typescript@5.9.3)
vite-svg-loader@5.1.0(vue@3.5.26(typescript@5.9.3)):
@@ -19047,6 +19065,23 @@ snapshots:
sass: 1.97.1
terser: 5.44.1
vite@6.4.1(@types/node@20.19.27)(jiti@1.21.7)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2):
dependencies:
esbuild: 0.25.12
fdir: 6.5.0(picomatch@4.0.3)
picomatch: 4.0.3
postcss: 8.5.6
rollup: 4.54.0
tinyglobby: 0.2.15
optionalDependencies:
'@types/node': 20.19.27
fsevents: 2.3.3
jiti: 1.21.7
lightningcss: 1.30.2
sass: 1.97.1
terser: 5.44.1
yaml: 2.8.2
vite@6.4.1(@types/node@20.19.27)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2):
dependencies:
esbuild: 0.25.12
@@ -19064,23 +19099,6 @@ snapshots:
terser: 5.44.1
yaml: 2.8.2
vite@7.3.0(@types/node@20.19.27)(jiti@1.21.7)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2):
dependencies:
esbuild: 0.27.2
fdir: 6.5.0(picomatch@4.0.3)
picomatch: 4.0.3
postcss: 8.5.6
rollup: 4.54.0
tinyglobby: 0.2.15
optionalDependencies:
'@types/node': 20.19.27
fsevents: 2.3.3
jiti: 1.21.7
lightningcss: 1.30.2
sass: 1.97.1
terser: 5.44.1
yaml: 2.8.2
vite@7.3.0(@types/node@20.19.27)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2):
dependencies:
esbuild: 0.27.2
@@ -19102,10 +19120,10 @@ snapshots:
optionalDependencies:
vite: 6.4.1(@types/node@20.19.27)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2)
vitest@4.0.16(@opentelemetry/api@1.9.0)(@types/node@20.19.27)(@vitest/browser-playwright@4.0.16)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2):
vitest@4.0.16(@opentelemetry/api@1.9.0)(@types/node@20.19.27)(@vitest/browser-playwright@4.0.16)(jiti@1.21.7)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2):
dependencies:
'@vitest/expect': 4.0.16
'@vitest/mocker': 4.0.16(vite@6.4.1(@types/node@20.19.27)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2))
'@vitest/mocker': 4.0.16(vite@6.4.1(@types/node@20.19.27)(jiti@1.21.7)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2))
'@vitest/pretty-format': 4.0.16
'@vitest/runner': 4.0.16
'@vitest/snapshot': 4.0.16
@@ -19122,7 +19140,7 @@ snapshots:
tinyexec: 1.0.2
tinyglobby: 0.2.15
tinyrainbow: 3.0.3
vite: 6.4.1(@types/node@20.19.27)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2)
vite: 6.4.1(@types/node@20.19.27)(jiti@1.21.7)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2)
why-is-node-running: 2.3.0
optionalDependencies:
'@opentelemetry/api': 1.9.0
@@ -19257,7 +19275,7 @@ snapshots:
vue-component-type-helpers@3.2.1: {}
vue-component-type-helpers@3.2.2: {}
vue-component-type-helpers@3.2.4: {}
vue-confetti-explosion@1.0.2(vue@3.5.26(typescript@5.9.3)):
dependencies: