From 31f2c7b5053c83a9faf64246e3bf2fd83189401c Mon Sep 17 00:00:00 2001 From: Jason Rasmussen Date: Thu, 4 Dec 2025 11:09:38 -0500 Subject: [PATCH 01/35] feat: header context menu (#24374) --- i18n/en.json | 1 - pnpm-lock.yaml | 10 ++-- web/package.json | 2 +- .../lib/components/HeaderActionButton.svelte | 24 +++++++++ web/src/lib/components/HeaderButton.svelte | 17 ------ .../components/layouts/AdminPageLayout.svelte | 52 ++++++++++++++++--- .../lib/components/layouts/TitleLayout.svelte | 21 -------- web/src/lib/services/library.service.ts | 33 +++++------- web/src/lib/services/queue.service.ts | 50 +++++++++++++----- web/src/lib/services/shared-link.service.ts | 14 +++-- web/src/lib/services/system-config.service.ts | 2 +- web/src/lib/services/user-admin.service.ts | 29 ++++++----- web/src/lib/types.ts | 3 ++ web/src/routes/+layout.svelte | 21 ++++++-- .../admin/library-management/+page.svelte | 11 +--- .../routes/admin/library-management/+page.ts | 2 +- .../library-management/[id]/+page.svelte | 19 ++----- web/src/routes/admin/queues/+page.svelte | 43 ++------------- .../routes/admin/queues/[name]/+page.svelte | 27 ++++++---- .../routes/admin/system-settings/+page.svelte | 20 ++----- web/src/routes/admin/users/+page.svelte | 10 +--- web/src/routes/admin/users/[id]/+page.svelte | 27 ++-------- 22 files changed, 208 insertions(+), 230 deletions(-) create mode 100644 web/src/lib/components/HeaderActionButton.svelte delete mode 100644 web/src/lib/components/HeaderButton.svelte delete mode 100644 web/src/lib/components/layouts/TitleLayout.svelte diff --git a/i18n/en.json b/i18n/en.json index 6495e45215..7eb9ffbef6 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -78,7 +78,6 @@ "exclusion_pattern_description": "Exclusion patterns lets you ignore files and folders when scanning your library. This is useful if you have folders that contain files you don't want to import, such as RAW files.", "export_config_as_json_description": "Download the current system config as a JSON file", "external_libraries_page_description": "Admin external library page", - "external_library_management": "External Library Management", "face_detection": "Face detection", "face_detection_description": "Detect the faces in assets using machine learning. For videos, only the thumbnail is considered. \"Refresh\" (re-)processes all assets. \"Reset\" additionally clears all current face data. \"Missing\" queues assets that haven't been processed yet. Detected faces will be queued for Facial Recognition after Face Detection is complete, grouping them into existing or new people.", "facial_recognition_job_description": "Group detected faces into people. This step runs after Face Detection is complete. \"Reset\" (re-)clusters all faces. \"Missing\" queues faces that don't have a person assigned.", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 43d2848e16..33afa6bc4a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -717,8 +717,8 @@ importers: specifier: file:../open-api/typescript-sdk version: link:../open-api/typescript-sdk '@immich/ui': - specifier: ^0.49.2 - version: 0.49.3(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2) + specifier: ^0.50.0 + version: 0.50.0(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2) '@mapbox/mapbox-gl-rtl-text': specifier: 0.2.3 version: 0.2.3(mapbox-gl@1.13.3) @@ -2989,8 +2989,8 @@ packages: peerDependencies: svelte: ^5.0.0 - '@immich/ui@0.49.3': - resolution: {integrity: sha512-joqT72Y6gmGK6z25Suzr2VhYANrLo43g20T4UHmbQenz/z/Ax6sl1Ao9SjIOwEkKMm9N3Txoh7WOOzmHVl04OA==} + '@immich/ui@0.50.0': + resolution: {integrity: sha512-7AW9SRZTAgal8xlkUAxm7o4+pSG7HcKb+Bh9JpWLaDRRdGyPCZMmsNa9CjZglOQ7wkAD07tQ9u4+zezBLe0dlQ==} peerDependencies: svelte: ^5.0.0 @@ -14700,7 +14700,7 @@ snapshots: dependencies: svelte: 5.45.2 - '@immich/ui@0.49.3(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2)': + '@immich/ui@0.50.0(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2)': dependencies: '@immich/svelte-markdown-preprocess': 0.1.0(svelte@5.45.2) '@internationalized/date': 3.10.0 diff --git a/web/package.json b/web/package.json index 2e7b740153..cfa0f5cc30 100644 --- a/web/package.json +++ b/web/package.json @@ -28,7 +28,7 @@ "@formatjs/icu-messageformat-parser": "^2.9.8", "@immich/justified-layout-wasm": "^0.4.3", "@immich/sdk": "file:../open-api/typescript-sdk", - "@immich/ui": "^0.49.2", + "@immich/ui": "^0.50.0", "@mapbox/mapbox-gl-rtl-text": "0.2.3", "@mdi/js": "^7.4.47", "@photo-sphere-viewer/core": "^5.14.0", diff --git a/web/src/lib/components/HeaderActionButton.svelte b/web/src/lib/components/HeaderActionButton.svelte new file mode 100644 index 0000000000..542c22ba43 --- /dev/null +++ b/web/src/lib/components/HeaderActionButton.svelte @@ -0,0 +1,24 @@ + + +{#if action.$if?.() ?? true} + +{/if} diff --git a/web/src/lib/components/HeaderButton.svelte b/web/src/lib/components/HeaderButton.svelte deleted file mode 100644 index c4189c06c0..0000000000 --- a/web/src/lib/components/HeaderButton.svelte +++ /dev/null @@ -1,17 +0,0 @@ - - -{#if action.$if?.() ?? true} - -{/if} diff --git a/web/src/lib/components/layouts/AdminPageLayout.svelte b/web/src/lib/components/layouts/AdminPageLayout.svelte index 45d21c9139..d63e306853 100644 --- a/web/src/lib/components/layouts/AdminPageLayout.svelte +++ b/web/src/lib/components/layouts/AdminPageLayout.svelte @@ -1,19 +1,33 @@ @@ -24,11 +38,37 @@ - +
+
+ + + {#if actions.length > 0} + + + + {/if} +
{@render children?.()} - +
diff --git a/web/src/lib/components/layouts/TitleLayout.svelte b/web/src/lib/components/layouts/TitleLayout.svelte deleted file mode 100644 index 2d867bab2f..0000000000 --- a/web/src/lib/components/layouts/TitleLayout.svelte +++ /dev/null @@ -1,21 +0,0 @@ - - -
-
- - {@render buttons?.()} -
- {@render children?.()} -
diff --git a/web/src/lib/services/library.service.ts b/web/src/lib/services/library.service.ts index 8b4d35a5f6..d20eae6af6 100644 --- a/web/src/lib/services/library.service.ts +++ b/web/src/lib/services/library.service.ts @@ -28,7 +28,7 @@ export const getLibrariesActions = ($t: MessageFormatter, libraries: LibraryResp title: $t('scan_all_libraries'), type: $t('command'), icon: mdiSync, - onAction: () => void handleScanAllLibraries(), + onAction: () => handleScanAllLibraries(), shortcuts: { shift: true, key: 'r' }, $if: () => libraries.length > 0, }; @@ -37,7 +37,7 @@ export const getLibrariesActions = ($t: MessageFormatter, libraries: LibraryResp title: $t('create_library'), type: $t('command'), icon: mdiPlusBoxOutline, - onAction: () => void handleCreateLibrary(), + onAction: () => handleCreateLibrary(), shortcuts: { shift: true, key: 'n' }, }; @@ -49,7 +49,7 @@ export const getLibraryActions = ($t: MessageFormatter, library: LibraryResponse icon: mdiPencilOutline, type: $t('command'), title: $t('rename'), - onAction: () => void modalManager.show(LibraryRenameModal, { library }), + onAction: () => modalManager.show(LibraryRenameModal, { library }), shortcuts: { key: 'r' }, }; @@ -58,7 +58,7 @@ export const getLibraryActions = ($t: MessageFormatter, library: LibraryResponse type: $t('command'), title: $t('delete'), color: 'danger', - onAction: () => void handleDeleteLibrary(library), + onAction: () => handleDeleteLibrary(library), shortcuts: { key: 'Backspace' }, }; @@ -66,21 +66,21 @@ export const getLibraryActions = ($t: MessageFormatter, library: LibraryResponse icon: mdiPlusBoxOutline, type: $t('command'), title: $t('add'), - onAction: () => void modalManager.show(LibraryFolderAddModal, { library }), + onAction: () => modalManager.show(LibraryFolderAddModal, { library }), }; const AddExclusionPattern: ActionItem = { icon: mdiPlusBoxOutline, type: $t('command'), title: $t('add'), - onAction: () => void modalManager.show(LibraryExclusionPatternAddModal, { library }), + onAction: () => modalManager.show(LibraryExclusionPatternAddModal, { library }), }; const Scan: ActionItem = { icon: mdiSync, type: $t('command'), title: $t('scan_library'), - onAction: () => void handleScanLibrary(library), + onAction: () => handleScanLibrary(library), shortcuts: { shift: true, key: 'r' }, }; @@ -92,14 +92,14 @@ export const getLibraryFolderActions = ($t: MessageFormatter, library: LibraryRe icon: mdiPencilOutline, type: $t('command'), title: $t('edit'), - onAction: () => void modalManager.show(LibraryFolderEditModal, { folder, library }), + onAction: () => modalManager.show(LibraryFolderEditModal, { folder, library }), }; const Delete: ActionItem = { icon: mdiTrashCanOutline, type: $t('command'), title: $t('delete'), - onAction: () => void handleDeleteLibraryFolder(library, folder), + onAction: () => handleDeleteLibraryFolder(library, folder), }; return { Edit, Delete }; @@ -114,14 +114,14 @@ export const getLibraryExclusionPatternActions = ( icon: mdiPencilOutline, type: $t('command'), title: $t('edit'), - onAction: () => void modalManager.show(LibraryExclusionPatternEditModal, { exclusionPattern, library }), + onAction: () => modalManager.show(LibraryExclusionPatternEditModal, { exclusionPattern, library }), }; const Delete: ActionItem = { icon: mdiTrashCanOutline, type: $t('command'), title: $t('delete'), - onAction: () => void handleDeleteExclusionPattern(library, exclusionPattern), + onAction: () => handleDeleteExclusionPattern(library, exclusionPattern), }; return { Edit, Delete }; @@ -273,7 +273,7 @@ const handleDeleteLibraryFolder = async (library: LibraryResponseDto, folder: st }); if (!confirmed) { - return false; + return; } try { @@ -285,10 +285,7 @@ const handleDeleteLibraryFolder = async (library: LibraryResponseDto, folder: st toastManager.success($t('admin.library_updated')); } catch (error) { handleError(error, $t('errors.unable_to_update_library')); - return false; } - - return true; }; export const handleAddLibraryExclusionPattern = async (library: LibraryResponseDto, exclusionPattern: string) => { @@ -345,9 +342,8 @@ const handleDeleteExclusionPattern = async (library: LibraryResponseDto, exclusi const $t = await getFormatter(); const confirmed = await modalManager.showDialog({ prompt: $t('admin.library_remove_exclusion_pattern_prompt') }); - if (!confirmed) { - return false; + return; } try { @@ -361,8 +357,5 @@ const handleDeleteExclusionPattern = async (library: LibraryResponseDto, exclusi toastManager.success($t('admin.library_updated')); } catch (error) { handleError(error, $t('errors.unable_to_update_library')); - return false; } - - return true; }; diff --git a/web/src/lib/services/queue.service.ts b/web/src/lib/services/queue.service.ts index 2372461d1a..46219ef22a 100644 --- a/web/src/lib/services/queue.service.ts +++ b/web/src/lib/services/queue.service.ts @@ -1,11 +1,20 @@ import { goto } from '$app/navigation'; import { AppRoute } from '$lib/constants'; import { eventManager } from '$lib/managers/event-manager.svelte'; +import { queueManager } from '$lib/managers/queue-manager.svelte'; import JobCreateModal from '$lib/modals/JobCreateModal.svelte'; -import { user } from '$lib/stores/user.store'; +import type { HeaderButtonActionItem } from '$lib/types'; import { handleError } from '$lib/utils/handle-error'; import { getFormatter } from '$lib/utils/i18n'; -import { emptyQueue, getQueue, QueueName, updateQueue, type QueueResponseDto } from '@immich/sdk'; +import { + emptyQueue, + getQueue, + QueueCommand, + QueueName, + runQueueCommandLegacy, + updateQueue, + type QueueResponseDto, +} from '@immich/sdk'; import { modalManager, toastManager, type ActionItem, type IconLike } from '@immich/ui'; import { mdiClose, @@ -23,7 +32,6 @@ import { mdiPlay, mdiPlus, mdiStateMachine, - mdiSync, mdiTable, mdiTagFaces, mdiTrashCanOutline, @@ -31,7 +39,6 @@ import { mdiVideo, } from '@mdi/js'; import type { MessageFormatter } from 'svelte-i18n'; -import { get } from 'svelte/store'; type QueueItem = { icon: IconLike; @@ -39,15 +46,17 @@ type QueueItem = { subtitle?: string; }; -export const getQueuesActions = ($t: MessageFormatter) => { - const ViewQueues: ActionItem = { - title: $t('admin.queues'), - description: $t('admin.queues_page_description'), - icon: mdiSync, - type: $t('page'), - isGlobal: true, - $if: () => get(user)?.isAdmin, - onAction: () => goto(AppRoute.ADMIN_QUEUES), +export const getQueuesActions = ($t: MessageFormatter, queues: QueueResponseDto[] | undefined) => { + const pausedQueues = (queues ?? []).filter(({ isPaused }) => isPaused).map(({ name }) => name); + + const ResumePaused: HeaderButtonActionItem = { + title: $t('resume_paused_jobs', { values: { count: pausedQueues.length } }), + $if: () => pausedQueues.length > 0, + icon: mdiPlay, + onAction: () => handleResumePausedJobs(pausedQueues), + data: { + title: pausedQueues.join(', '), + }, }; const CreateJob: ActionItem = { @@ -68,7 +77,7 @@ export const getQueuesActions = ($t: MessageFormatter) => { onAction: () => goto(`${AppRoute.ADMIN_SETTINGS}?isOpen=job`), }; - return { ViewQueues, ManageConcurrency, CreateJob }; + return { ResumePaused, ManageConcurrency, CreateJob }; }; export const getQueueActions = ($t: MessageFormatter, queue: QueueResponseDto) => { @@ -126,6 +135,19 @@ export const handleEmptyQueue = async (queue: QueueResponseDto) => { } }; +const handleResumePausedJobs = async (queues: QueueName[]) => { + const $t = await getFormatter(); + + try { + for (const name of queues) { + await runQueueCommandLegacy({ name, queueCommandDto: { command: QueueCommand.Resume, force: false } }); + } + await queueManager.refresh(); + } catch (error) { + handleError(error, $t('admin.failed_job_command', { values: { command: 'resume', job: 'paused jobs' } })); + } +}; + const handleRemoveFailedJobs = async (queue: QueueResponseDto) => { const $t = await getFormatter(); diff --git a/web/src/lib/services/shared-link.service.ts b/web/src/lib/services/shared-link.service.ts index 4e6a942682..cbea6ddd9d 100644 --- a/web/src/lib/services/shared-link.service.ts +++ b/web/src/lib/services/shared-link.service.ts @@ -24,26 +24,26 @@ export const getSharedLinkActions = ($t: MessageFormatter, sharedLink: SharedLin const Edit: ActionItem = { title: $t('edit_link'), icon: mdiPencilOutline, - onAction: () => void goto(`${AppRoute.SHARED_LINKS}/${sharedLink.id}`), + onAction: () => goto(`${AppRoute.SHARED_LINKS}/${sharedLink.id}`), }; const Delete: ActionItem = { title: $t('delete_link'), icon: mdiTrashCanOutline, color: 'danger', - onAction: () => void handleDeleteSharedLink(sharedLink), + onAction: () => handleDeleteSharedLink(sharedLink), }; const Copy: ActionItem = { title: $t('copy_link'), icon: mdiContentCopy, - onAction: () => void copyToClipboard(asUrl(sharedLink)), + onAction: () => copyToClipboard(asUrl(sharedLink)), }; const ViewQrCode: ActionItem = { title: $t('view_qr_code'), icon: mdiQrcode, - onAction: () => void handleShowSharedLinkQrCode(sharedLink), + onAction: () => handleShowSharedLinkQrCode(sharedLink), }; return { Edit, Delete, Copy, ViewQrCode }; @@ -88,7 +88,7 @@ export const handleUpdateSharedLink = async (sharedLink: SharedLinkResponseDto, } }; -export const handleDeleteSharedLink = async (sharedLink: SharedLinkResponseDto): Promise => { +const handleDeleteSharedLink = async (sharedLink: SharedLinkResponseDto) => { const $t = await getFormatter(); const success = await modalManager.showDialog({ title: $t('delete_shared_link'), @@ -96,17 +96,15 @@ export const handleDeleteSharedLink = async (sharedLink: SharedLinkResponseDto): confirmText: $t('delete'), }); if (!success) { - return false; + return; } try { await removeSharedLink({ id: sharedLink.id }); eventManager.emit('SharedLinkDelete', sharedLink); toastManager.success($t('deleted_shared_link')); - return true; } catch (error) { handleError(error, $t('errors.unable_to_delete_shared_link')); - return false; } }; diff --git a/web/src/lib/services/system-config.service.ts b/web/src/lib/services/system-config.service.ts index ffd0094c72..b8c7716d47 100644 --- a/web/src/lib/services/system-config.service.ts +++ b/web/src/lib/services/system-config.service.ts @@ -20,7 +20,7 @@ export const getSystemConfigActions = ( description: $t('admin.copy_config_to_clipboard_description'), type: $t('command'), icon: mdiContentCopy, - onAction: () => void handleCopyToClipboard(config), + onAction: () => handleCopyToClipboard(config), shortcuts: { shift: true, key: 'c' }, }; diff --git a/web/src/lib/services/user-admin.service.ts b/web/src/lib/services/user-admin.service.ts index 7a49f2fbe3..997a43fc7f 100644 --- a/web/src/lib/services/user-admin.service.ts +++ b/web/src/lib/services/user-admin.service.ts @@ -1,11 +1,13 @@ import { goto } from '$app/navigation'; import { eventManager } from '$lib/managers/event-manager.svelte'; +import { serverConfigManager } from '$lib/managers/server-config-manager.svelte'; import PasswordResetSuccessModal from '$lib/modals/PasswordResetSuccessModal.svelte'; import UserCreateModal from '$lib/modals/UserCreateModal.svelte'; import UserDeleteConfirmModal from '$lib/modals/UserDeleteConfirmModal.svelte'; import UserEditModal from '$lib/modals/UserEditModal.svelte'; import UserRestoreConfirmModal from '$lib/modals/UserRestoreConfirmModal.svelte'; import { user as authUser } from '$lib/stores/user.store'; +import type { HeaderButtonActionItem } from '$lib/types'; import { handleError } from '$lib/utils/handle-error'; import { getFormatter } from '$lib/utils/i18n'; import { @@ -28,6 +30,7 @@ import { mdiPlusBoxOutline, mdiTrashCanOutline, } from '@mdi/js'; +import { DateTime } from 'luxon'; import type { MessageFormatter } from 'svelte-i18n'; import { get } from 'svelte/store'; @@ -36,7 +39,7 @@ export const getUserAdminsActions = ($t: MessageFormatter) => { title: $t('create_user'), type: $t('command'), icon: mdiPlusBoxOutline, - onAction: () => void modalManager.show(UserCreateModal, {}), + onAction: () => modalManager.show(UserCreateModal, {}), shortcuts: { shift: true, key: 'n' }, }; @@ -60,11 +63,17 @@ export const getUserAdminActions = ($t: MessageFormatter, user: UserAdminRespons shortcuts: { key: 'Backspace' }, }; - const Restore: ActionItem = { + const getDeleteDate = (deletedAt: string): Date => + DateTime.fromISO(deletedAt).plus({ days: serverConfigManager.value.userDeleteDelay }).toJSDate(); + + const Restore: HeaderButtonActionItem = { icon: mdiDeleteRestore, title: $t('restore'), type: $t('command'), color: 'primary', + data: { + title: $t('admin.user_restore_scheduled_removal', { values: { date: getDeleteDate(user.deletedAt!) } }), + }, $if: () => !!user.deletedAt && user.status === UserStatus.Deleted, onAction: () => modalManager.show(UserRestoreConfirmModal, { user }), }; @@ -74,14 +83,14 @@ export const getUserAdminActions = ($t: MessageFormatter, user: UserAdminRespons title: $t('reset_password'), type: $t('command'), $if: () => get(authUser).id !== user.id, - onAction: () => void handleResetPasswordUserAdmin(user), + onAction: () => handleResetPasswordUserAdmin(user), }; const ResetPinCode: ActionItem = { icon: mdiLockSmart, type: $t('command'), title: $t('reset_pin_code'), - onAction: () => void handleResetPinCodeUserAdmin(user), + onAction: () => handleResetPinCodeUserAdmin(user), }; return { Update, Delete, Restore, ResetPassword, ResetPinCode }; @@ -162,12 +171,12 @@ const generatePassword = (length: number = 16) => { return generatedPassword; }; -export const handleResetPasswordUserAdmin = async (user: UserAdminResponseDto) => { +const handleResetPasswordUserAdmin = async (user: UserAdminResponseDto) => { const $t = await getFormatter(); const prompt = $t('admin.confirm_user_password_reset', { values: { user: user.name } }); const success = await modalManager.showDialog({ prompt }); if (!success) { - return false; + return; } try { @@ -176,28 +185,24 @@ export const handleResetPasswordUserAdmin = async (user: UserAdminResponseDto) = eventManager.emit('UserAdminUpdate', response); toastManager.success(); await modalManager.show(PasswordResetSuccessModal, { newPassword: dto.password }); - return true; } catch (error) { handleError(error, $t('errors.unable_to_reset_password')); - return false; } }; -export const handleResetPinCodeUserAdmin = async (user: UserAdminResponseDto) => { +const handleResetPinCodeUserAdmin = async (user: UserAdminResponseDto) => { const $t = await getFormatter(); const prompt = $t('admin.confirm_user_pin_code_reset', { values: { user: user.name } }); const success = await modalManager.showDialog({ prompt }); if (!success) { - return false; + return; } try { const response = await updateUserAdmin({ id: user.id, userAdminUpdateDto: { pinCode: null } }); eventManager.emit('UserAdminUpdate', response); toastManager.success($t('pin_code_reset_successfully')); - return true; } catch (error) { handleError(error, $t('errors.unable_to_reset_pin_code')); - return false; } }; diff --git a/web/src/lib/types.ts b/web/src/lib/types.ts index e7d38b1a25..dbe3c851a0 100644 --- a/web/src/lib/types.ts +++ b/web/src/lib/types.ts @@ -1,4 +1,5 @@ import type { QueueResponseDto, ServerVersionResponseDto } from '@immich/sdk'; +import type { ActionItem } from '@immich/ui'; export interface ReleaseEvent { isAvailable: boolean; @@ -9,3 +10,5 @@ export interface ReleaseEvent { } export type QueueSnapshot = { timestamp: number; snapshot?: QueueResponseDto[] }; + +export type HeaderButtonActionItem = ActionItem & { data?: { title?: string } }; diff --git a/web/src/routes/+layout.svelte b/web/src/routes/+layout.svelte index c8f41b6fbc..77a3d402b2 100644 --- a/web/src/routes/+layout.svelte +++ b/web/src/routes/+layout.svelte @@ -14,15 +14,15 @@ import { themeManager } from '$lib/managers/theme-manager.svelte'; import ServerRestartingModal from '$lib/modals/ServerRestartingModal.svelte'; import VersionAnnouncementModal from '$lib/modals/VersionAnnouncementModal.svelte'; - import { getQueuesActions } from '$lib/services/queue.service'; + import { sidebarStore } from '$lib/stores/sidebar.svelte'; import { user } from '$lib/stores/user.store'; import { closeWebsocketConnection, openWebsocketConnection, websocketStore } from '$lib/stores/websocket'; import type { ReleaseEvent } from '$lib/types'; import { copyToClipboard, getReleaseType, semverToName } from '$lib/utils'; import { maintenanceShouldRedirect } from '$lib/utils/maintenance'; import { isAssetViewerRoute } from '$lib/utils/navigation'; - import { CommandPaletteContext, modalManager, setTranslations, type ActionItem } from '@immich/ui'; - import { mdiAccountMultipleOutline, mdiBookshelf, mdiCog, mdiServer, mdiThemeLightDark } from '@mdi/js'; + import { CommandPaletteContext, modalManager, setTranslations, toastManager, type ActionItem } from '@immich/ui'; + import { mdiAccountMultipleOutline, mdiBookshelf, mdiCog, mdiServer, mdiSync, mdiThemeLightDark } from '@mdi/js'; import { onMount, type Snippet } from 'svelte'; import { t } from 'svelte-i18n'; import '../app.css'; @@ -53,6 +53,8 @@ return new URL(page.url.pathname + page.url.search, 'https://my.immich.app'); }; + toastManager.setOptions({ class: 'top-16' }); + onMount(() => { const element = document.querySelector('#stencil'); element?.remove(); @@ -62,6 +64,10 @@ eventManager.emit('AppInit'); beforeNavigate(({ from, to }) => { + if (sidebarStore.isOpen) { + sidebarStore.reset(); + } + if (isAssetViewerRoute(from) && isAssetViewerRoute(to)) { return; } @@ -149,6 +155,13 @@ icon: mdiCog, onAction: () => goto(AppRoute.ADMIN_SETTINGS), }, + { + title: $t('admin.queues'), + description: $t('admin.queues_page_description'), + icon: mdiSync, + type: $t('page'), + onAction: () => goto(AppRoute.ADMIN_QUEUES), + }, { title: $t('external_libraries'), description: $t('admin.external_libraries_page_description'), @@ -163,7 +176,7 @@ }, ].map((route) => ({ ...route, type: $t('page'), isGlobal: true, $if: () => $user?.isAdmin })); - const commands = $derived([...userCommands, ...adminCommands, ...Object.values(getQueuesActions($t))]); + const commands = $derived([...userCommands, ...adminCommands]); diff --git a/web/src/routes/admin/library-management/+page.svelte b/web/src/routes/admin/library-management/+page.svelte index aef8447d00..9aa5af6481 100644 --- a/web/src/routes/admin/library-management/+page.svelte +++ b/web/src/routes/admin/library-management/+page.svelte @@ -1,6 +1,5 @@ - {#snippet buttons()} - - - - - - - - {/snippet}
{#if user.deletedAt} From ae8f5a6673a0166082d79aad539dad47e64e8034 Mon Sep 17 00:00:00 2001 From: Daniel Dietzler <36593685+danieldietzler@users.noreply.github.com> Date: Thu, 4 Dec 2025 17:10:42 +0100 Subject: [PATCH 02/35] fix: prettier (#24386) --- .github/package.json | 2 +- cli/package.json | 2 +- docs/package.json | 2 +- e2e/package.json | 2 +- pnpm-lock.yaml | 68 ++++++++++++++++++++++---------------------- server/package.json | 2 +- web/package.json | 2 +- 7 files changed, 40 insertions(+), 40 deletions(-) diff --git a/.github/package.json b/.github/package.json index 1cb0262c74..9b41cc7b4e 100644 --- a/.github/package.json +++ b/.github/package.json @@ -4,6 +4,6 @@ "format:fix": "prettier --write ." }, "devDependencies": { - "prettier": "^3.5.3" + "prettier": "^3.7.4" } } diff --git a/cli/package.json b/cli/package.json index b64354ee4a..e74425eb41 100644 --- a/cli/package.json +++ b/cli/package.json @@ -31,7 +31,7 @@ "eslint-plugin-unicorn": "^62.0.0", "globals": "^16.0.0", "mock-fs": "^5.2.0", - "prettier": "^3.2.5", + "prettier": "^3.7.4", "prettier-plugin-organize-imports": "^4.0.0", "typescript": "^5.3.3", "typescript-eslint": "^8.28.0", diff --git a/docs/package.json b/docs/package.json index b96059c523..d37b256a3f 100644 --- a/docs/package.json +++ b/docs/package.json @@ -38,7 +38,7 @@ "@docusaurus/module-type-aliases": "~3.9.0", "@docusaurus/tsconfig": "^3.7.0", "@docusaurus/types": "^3.7.0", - "prettier": "^3.2.4", + "prettier": "^3.7.4", "typescript": "^5.1.6" }, "browserslist": { diff --git a/e2e/package.json b/e2e/package.json index 7bf61ea232..e82ca07b78 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -43,7 +43,7 @@ "oidc-provider": "^9.0.0", "pg": "^8.11.3", "pngjs": "^7.0.0", - "prettier": "^3.2.5", + "prettier": "^3.7.4", "prettier-plugin-organize-imports": "^4.0.0", "sharp": "^0.34.5", "socket.io-client": "^4.7.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 33afa6bc4a..db215b6035 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -20,8 +20,8 @@ importers: .github: devDependencies: prettier: - specifier: ^3.5.3 - version: 3.7.1 + specifier: ^3.7.4 + version: 3.7.4 cli: dependencies: @@ -85,7 +85,7 @@ importers: version: 10.1.8(eslint@9.39.1(jiti@2.6.1)) eslint-plugin-prettier: specifier: ^5.1.3 - version: 5.5.4(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1))(prettier@3.7.1) + version: 5.5.4(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1))(prettier@3.7.4) eslint-plugin-unicorn: specifier: ^62.0.0 version: 62.0.0(eslint@9.39.1(jiti@2.6.1)) @@ -96,11 +96,11 @@ importers: specifier: ^5.2.0 version: 5.5.0 prettier: - specifier: ^3.2.5 - version: 3.7.1 + specifier: ^3.7.4 + version: 3.7.4 prettier-plugin-organize-imports: specifier: ^4.0.0 - version: 4.3.0(prettier@3.7.1)(typescript@5.9.3) + version: 4.3.0(prettier@3.7.4)(typescript@5.9.3) typescript: specifier: ^5.3.3 version: 5.9.3 @@ -184,8 +184,8 @@ importers: specifier: ^3.7.0 version: 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) prettier: - specifier: ^3.2.4 - version: 3.7.1 + specifier: ^3.7.4 + version: 3.7.4 typescript: specifier: ^5.1.6 version: 5.9.3 @@ -239,7 +239,7 @@ importers: version: 10.1.8(eslint@9.39.1(jiti@2.6.1)) eslint-plugin-prettier: specifier: ^5.1.3 - version: 5.5.4(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1))(prettier@3.7.1) + version: 5.5.4(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1))(prettier@3.7.4) eslint-plugin-unicorn: specifier: ^62.0.0 version: 62.0.0(eslint@9.39.1(jiti@2.6.1)) @@ -265,11 +265,11 @@ importers: specifier: ^7.0.0 version: 7.0.0 prettier: - specifier: ^3.2.5 - version: 3.7.1 + specifier: ^3.7.4 + version: 3.7.4 prettier-plugin-organize-imports: specifier: ^4.0.0 - version: 4.3.0(prettier@3.7.1)(typescript@5.9.3) + version: 4.3.0(prettier@3.7.4)(typescript@5.9.3) sharp: specifier: ^0.34.5 version: 0.34.5 @@ -655,7 +655,7 @@ importers: version: 10.1.8(eslint@9.39.1(jiti@2.6.1)) eslint-plugin-prettier: specifier: ^5.1.3 - version: 5.5.4(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1))(prettier@3.7.1) + version: 5.5.4(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1))(prettier@3.7.4) eslint-plugin-unicorn: specifier: ^62.0.0 version: 62.0.0(eslint@9.39.1(jiti@2.6.1)) @@ -672,11 +672,11 @@ importers: specifier: ^7.0.0 version: 7.0.0 prettier: - specifier: ^3.0.2 - version: 3.7.1 + specifier: ^3.7.4 + version: 3.7.4 prettier-plugin-organize-imports: specifier: ^4.0.0 - version: 4.3.0(prettier@3.7.1)(typescript@5.9.3) + version: 4.3.0(prettier@3.7.4)(typescript@5.9.3) sql-formatter: specifier: ^15.0.0 version: 15.6.10 @@ -904,17 +904,17 @@ importers: specifier: ^16.0.0 version: 16.5.0 prettier: - specifier: ^3.4.2 - version: 3.7.1 + specifier: ^3.7.4 + version: 3.7.4 prettier-plugin-organize-imports: specifier: ^4.0.0 - version: 4.3.0(prettier@3.7.1)(typescript@5.9.3) + version: 4.3.0(prettier@3.7.4)(typescript@5.9.3) prettier-plugin-sort-json: specifier: ^4.1.1 - version: 4.1.1(prettier@3.7.1) + version: 4.1.1(prettier@3.7.4) prettier-plugin-svelte: specifier: ^3.3.3 - version: 3.4.0(prettier@3.7.1)(svelte@5.45.2) + version: 3.4.0(prettier@3.7.4)(svelte@5.45.2) rollup-plugin-visualizer: specifier: ^6.0.0 version: 6.0.5(rollup@4.53.3) @@ -9765,8 +9765,8 @@ packages: prettier: ^3.0.0 svelte: ^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0 - prettier@3.7.1: - resolution: {integrity: sha512-RWKXE4qB3u5Z6yz7omJkjWwmTfLdcbv44jUVHC5NpfXwFGzvpQM798FGv/6WNK879tc+Cn0AAyherCl1KjbyZQ==} + prettier@3.7.4: + resolution: {integrity: sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==} engines: {node: '>=14'} hasBin: true @@ -14517,7 +14517,7 @@ snapshots: '@fig/complete-commander@3.2.0(commander@11.1.0)': dependencies: commander: 11.1.0 - prettier: 3.7.1 + prettier: 3.7.4 '@floating-ui/core@1.7.3': dependencies: @@ -15788,7 +15788,7 @@ snapshots: '@react-email/render@1.4.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: html-to-text: 9.0.5 - prettier: 3.7.1 + prettier: 3.7.4 react: 19.2.0 react-dom: 19.2.0(react@19.2.0) react-promise-suspense: 0.3.4 @@ -18907,10 +18907,10 @@ snapshots: lodash.memoize: 4.1.2 semver: 7.7.3 - eslint-plugin-prettier@5.5.4(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1))(prettier@3.7.1): + eslint-plugin-prettier@5.5.4(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1))(prettier@3.7.4): dependencies: eslint: 9.39.1(jiti@2.6.1) - prettier: 3.7.1 + prettier: 3.7.4 prettier-linter-helpers: 1.0.0 synckit: 0.11.11 optionalDependencies: @@ -22636,21 +22636,21 @@ snapshots: dependencies: fast-diff: 1.3.0 - prettier-plugin-organize-imports@4.3.0(prettier@3.7.1)(typescript@5.9.3): + prettier-plugin-organize-imports@4.3.0(prettier@3.7.4)(typescript@5.9.3): dependencies: - prettier: 3.7.1 + prettier: 3.7.4 typescript: 5.9.3 - prettier-plugin-sort-json@4.1.1(prettier@3.7.1): + prettier-plugin-sort-json@4.1.1(prettier@3.7.4): dependencies: - prettier: 3.7.1 + prettier: 3.7.4 - prettier-plugin-svelte@3.4.0(prettier@3.7.1)(svelte@5.45.2): + prettier-plugin-svelte@3.4.0(prettier@3.7.4)(svelte@5.45.2): dependencies: - prettier: 3.7.1 + prettier: 3.7.4 svelte: 5.45.2 - prettier@3.7.1: {} + prettier@3.7.4: {} pretty-error@4.0.0: dependencies: diff --git a/server/package.json b/server/package.json index 915e45c116..617e52cd14 100644 --- a/server/package.json +++ b/server/package.json @@ -153,7 +153,7 @@ "mock-fs": "^5.2.0", "node-gyp": "^12.0.0", "pngjs": "^7.0.0", - "prettier": "^3.0.2", + "prettier": "^3.7.4", "prettier-plugin-organize-imports": "^4.0.0", "sql-formatter": "^15.0.0", "supertest": "^7.1.0", diff --git a/web/package.json b/web/package.json index cfa0f5cc30..82065d74bf 100644 --- a/web/package.json +++ b/web/package.json @@ -93,7 +93,7 @@ "factory.ts": "^1.4.1", "globals": "^16.0.0", "happy-dom": "^20.0.0", - "prettier": "^3.4.2", + "prettier": "^3.7.4", "prettier-plugin-organize-imports": "^4.0.0", "prettier-plugin-sort-json": "^4.1.1", "prettier-plugin-svelte": "^3.3.3", From 75a7c9c06c81a065a4cdd8fcf303cc7b118e8a28 Mon Sep 17 00:00:00 2001 From: Daniel Dietzler <36593685+danieldietzler@users.noreply.github.com> Date: Thu, 4 Dec 2025 18:54:20 +0100 Subject: [PATCH 03/35] feat: sql tools array as default value (#24389) --- .../sql-tools/decorators/column.decorator.ts | 2 +- server/src/sql-tools/helpers.ts | 4 ++ server/src/sql-tools/schema-diff.spec.ts | 14 +++++++ .../sql-tools/column-default-array.stub.ts | 40 +++++++++++++++++++ 4 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 server/test/sql-tools/column-default-array.stub.ts diff --git a/server/src/sql-tools/decorators/column.decorator.ts b/server/src/sql-tools/decorators/column.decorator.ts index adb3d0ed59..e5a0eb52f8 100644 --- a/server/src/sql-tools/decorators/column.decorator.ts +++ b/server/src/sql-tools/decorators/column.decorator.ts @@ -2,7 +2,7 @@ import { asOptions } from 'src/sql-tools/helpers'; import { register } from 'src/sql-tools/register'; import { ColumnStorage, ColumnType, DatabaseEnum } from 'src/sql-tools/types'; -export type ColumnValue = null | boolean | string | number | object | Date | (() => string); +export type ColumnValue = null | boolean | string | number | Array | object | Date | (() => string); export type ColumnBaseOptions = { name?: string; diff --git a/server/src/sql-tools/helpers.ts b/server/src/sql-tools/helpers.ts index 2ef35ce9ba..e0daf8262f 100644 --- a/server/src/sql-tools/helpers.ts +++ b/server/src/sql-tools/helpers.ts @@ -39,6 +39,10 @@ export const fromColumnValue = (columnValue?: ColumnValue) => { return `'${value.toISOString()}'`; } + if (Array.isArray(value)) { + return "'{}'"; + } + return `'${String(value)}'`; }; diff --git a/server/src/sql-tools/schema-diff.spec.ts b/server/src/sql-tools/schema-diff.spec.ts index fe249b4e29..f45fb98bd3 100644 --- a/server/src/sql-tools/schema-diff.spec.ts +++ b/server/src/sql-tools/schema-diff.spec.ts @@ -394,6 +394,20 @@ describe(schemaDiff.name, () => { expect(diff.items).toEqual([]); }); + + it('should support arrays, ignoring types', () => { + const diff = schemaDiff( + fromColumn({ name: 'column1', type: 'character varying', isArray: true, default: "'{}'" }), + fromColumn({ + name: 'column1', + type: 'character varying', + isArray: true, + default: "'{}'::character varying[]", + }), + ); + + expect(diff.items).toEqual([]); + }); }); }); diff --git a/server/test/sql-tools/column-default-array.stub.ts b/server/test/sql-tools/column-default-array.stub.ts new file mode 100644 index 0000000000..b5e9b7d04a --- /dev/null +++ b/server/test/sql-tools/column-default-array.stub.ts @@ -0,0 +1,40 @@ +import { Column, DatabaseSchema, Table } from 'src/sql-tools'; + +@Table() +export class Table1 { + @Column({ type: 'character varying', array: true, default: [] }) + column1!: string[]; +} + +export const description = 'should register a table with a column with a default value (array)'; +export const schema: DatabaseSchema = { + databaseName: 'postgres', + schemaName: 'public', + functions: [], + enums: [], + extensions: [], + parameters: [], + overrides: [], + tables: [ + { + name: 'table1', + columns: [ + { + name: 'column1', + tableName: 'table1', + type: 'character varying', + nullable: false, + isArray: true, + primary: false, + synchronize: true, + default: "'{}'", + }, + ], + indexes: [], + triggers: [], + constraints: [], + synchronize: true, + }, + ], + warnings: [], +}; From 146bf65d02cf5e344ff0b81a0c23c84a0cd2485b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Fri, 5 Dec 2025 15:26:20 +0100 Subject: [PATCH 04/35] refactor(dev): remove ulimits for rootless docker (#24393) Description ----------- When I follow the [developer setup](https://docs.immich.app/developer/setup) I run into a permission error using rootless docker. A while ago I asked on Discord in [#contributing](https://discord.com/channels/979116623879368755/1071165397228855327/1442974448776122592) about these ulimits. I suggest to remove the `ulimits` altogether. It seems that @ItalyPaleAle has left the setting just hoping that it could help somebody in the future. See the [PR description](https://github.com/immich-app/immich/pull/4556). How Has This Been Tested? ------------------------- Using rootless docker: ``` $ docker context ls NAME DESCRIPTION DOCKER ENDPOINT ERROR default unix:///var/run/docker.sock rootless * unix:///run/user/1000/docker.sock ``` Running `make` will fail because of permission errors: ``` $ docker compose -f ./docker/docker-compose.dev.yml up --remove-orphans ... Error response from daemon: failed to create task for container: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: error during container init: error setting rlimits for ready process: error setting rlimit type 7: operation not permitted ``` On my machine I have the following hard limit for "Maximum number of open file descriptors": ``` $ ulimit -nH 524288 ``` I can confirm that the permission error is caused by the security restrictions of the operating system mentioned above: Changing `docker/docker-compose.dev.yml` like .. ``` ulimits: nofile: soft: 524289 hard: 524289 ``` .. will lead to a permission error whereas this .. ``` ulimits: nofile: soft: 524288 hard: 524288 ``` .. starts fine. Apparently the defaults for these limits are coming from [systemd](https://github.com/systemd/systemd/blob/26b2085d54ebbfca8637362eafcb4a8e3faf832f/man/systemd.exec.xml#L1122) which is used on nearly every linux distribution. So my assumption is that almost any linux user who uses rootless docker will run into a permission error when starting the development setup. Checklist: ---------- - [x] I have performed a self-review of my own code - [x] I have made corresponding changes to the documentation if applicable - [x] I have no unrelated changes in the PR. - [ ] I have confirmed that any new dependencies are strictly necessary. - [ ] I have written tests for new code (if applicable) - [ ] I have followed naming conventions/patterns in the surrounding code - [ ] All code in `src/services/` uses repositories implementations for database calls, filesystem operations, etc. - [ ] All code in `src/repositories/` is pretty basic/simple and does not have any immich specific logic (that belongs in `src/services/`) --- docker/docker-compose.dev.yml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/docker/docker-compose.dev.yml b/docker/docker-compose.dev.yml index 6fa1c51bdd..4b1a69d133 100644 --- a/docker/docker-compose.dev.yml +++ b/docker/docker-compose.dev.yml @@ -58,10 +58,6 @@ services: IMMICH_THIRD_PARTY_BUG_FEATURE_URL: https://github.com/immich-app/immich/issues IMMICH_THIRD_PARTY_DOCUMENTATION_URL: https://docs.immich.app IMMICH_THIRD_PARTY_SUPPORT_URL: https://docs.immich.app/community-guides - ulimits: - nofile: - soft: 1048576 - hard: 1048576 ports: - 9230:9230 - 9231:9231 @@ -100,10 +96,6 @@ services: - app-node_modules:/usr/src/app/node_modules - sveltekit:/usr/src/app/web/.svelte-kit - coverage:/usr/src/app/web/coverage - ulimits: - nofile: - soft: 1048576 - hard: 1048576 restart: unless-stopped depends_on: immich-server: From 8f1669efbe446f608c8007c099051f6530b9d903 Mon Sep 17 00:00:00 2001 From: Hai Sullivan Date: Sat, 6 Dec 2025 04:02:04 +1100 Subject: [PATCH 05/35] chore(mobile): smoother UI experience for iOS devices (#24397) allows the tab pages to use the standard Material page transition during push/pop navigation --- mobile/lib/routing/router.dart | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/mobile/lib/routing/router.dart b/mobile/lib/routing/router.dart index 30f43cf3b2..383f599331 100644 --- a/mobile/lib/routing/router.dart +++ b/mobile/lib/routing/router.dart @@ -167,7 +167,7 @@ class AppRouter extends RootStackRouter { AutoRoute(page: LoginRoute.page, guards: [_duplicateGuard]), AutoRoute(page: ChangePasswordRoute.page), AutoRoute(page: SearchRoute.page, guards: [_authGuard, _duplicateGuard], maintainState: false), - CustomRoute( + AutoRoute( page: TabControllerRoute.page, guards: [_authGuard, _duplicateGuard], children: [ @@ -176,9 +176,8 @@ class AppRouter extends RootStackRouter { AutoRoute(page: LibraryRoute.page, guards: [_authGuard, _duplicateGuard]), AutoRoute(page: AlbumsRoute.page, guards: [_authGuard, _duplicateGuard]), ], - transitionsBuilder: TransitionsBuilders.fadeIn, ), - CustomRoute( + AutoRoute( page: TabShellRoute.page, guards: [_authGuard, _duplicateGuard], children: [ @@ -187,7 +186,6 @@ class AppRouter extends RootStackRouter { AutoRoute(page: DriftLibraryRoute.page, guards: [_authGuard, _duplicateGuard]), AutoRoute(page: DriftAlbumsRoute.page, guards: [_authGuard, _duplicateGuard]), ], - transitionsBuilder: TransitionsBuilders.fadeIn, ), CustomRoute( page: GalleryViewerRoute.page, From 3c80049192e3906609f8837828ee1ff021758a4b Mon Sep 17 00:00:00 2001 From: idubnori Date: Sat, 6 Dec 2025 04:51:59 +0900 Subject: [PATCH 06/35] chore(mobile): add kebabu menu in asset viewer (#24387) * feat(mobile): implement viewer kebab menu with about option * feat: revert exisitng buttons, adjust label name * unify MenuAnchor usage --------- Co-authored-by: Alex --- .../pages/drift_activities.page.dart | 2 +- .../add_action_button.widget.dart | 148 +++++++++--------- .../base_action_button.widget.dart | 21 ++- .../cast_action_button.widget.dart | 4 +- .../download_action_button.widget.dart | 4 +- .../favorite_action_button.widget.dart | 4 +- .../like_activity_action_button.widget.dart | 5 +- .../motion_photo_action_button.widget.dart | 4 +- .../unfavorite_action_button.widget.dart | 4 +- .../asset_viewer/top_app_bar.widget.dart | 31 ++-- .../viewer_kebab_menu.widget.dart | 47 ++++++ 11 files changed, 168 insertions(+), 106 deletions(-) create mode 100644 mobile/lib/presentation/widgets/asset_viewer/viewer_kebab_menu.widget.dart diff --git a/mobile/lib/presentation/pages/drift_activities.page.dart b/mobile/lib/presentation/pages/drift_activities.page.dart index b92d429aa1..ac0cd7f309 100644 --- a/mobile/lib/presentation/pages/drift_activities.page.dart +++ b/mobile/lib/presentation/pages/drift_activities.page.dart @@ -37,7 +37,7 @@ class DriftActivitiesPage extends HookConsumerWidget { child: Scaffold( appBar: AppBar( title: Text(album.name), - actions: [const LikeActivityActionButton(menuItem: true)], + actions: [const LikeActivityActionButton(iconOnly: true)], actionsPadding: const EdgeInsets.only(right: 8), ), body: activities.widgetWhen( diff --git a/mobile/lib/presentation/widgets/action_buttons/add_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/add_action_button.widget.dart index 71fedf1258..054f058739 100644 --- a/mobile/lib/presentation/widgets/action_buttons/add_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/add_action_button.widget.dart @@ -21,12 +21,34 @@ import 'package:immich_mobile/presentation/widgets/bottom_sheet/base_bottom_shee enum AddToMenuItem { album, archive, unarchive, lockedFolder } -class AddActionButton extends ConsumerWidget { +class AddActionButton extends ConsumerStatefulWidget { const AddActionButton({super.key}); - Future _showAddOptions(BuildContext context, WidgetRef ref) async { + @override + ConsumerState createState() => _AddActionButtonState(); +} + +class _AddActionButtonState extends ConsumerState { + void _handleMenuSelection(AddToMenuItem selected) { + switch (selected) { + case AddToMenuItem.album: + _openAlbumSelector(); + break; + case AddToMenuItem.archive: + performArchiveAction(context, ref, source: ActionSource.viewer); + break; + case AddToMenuItem.unarchive: + performUnArchiveAction(context, ref, source: ActionSource.viewer); + break; + case AddToMenuItem.lockedFolder: + performMoveToLockFolderAction(context, ref, source: ActionSource.viewer); + break; + } + } + + List _buildMenuChildren() { final asset = ref.read(currentAssetNotifier); - if (asset == null) return; + if (asset == null) return []; final user = ref.read(currentUserProvider); final isOwner = asset is RemoteAsset && asset.ownerId == user?.id; @@ -35,93 +57,57 @@ class AddActionButton extends ConsumerWidget { final hasRemote = asset is RemoteAsset; final showArchive = isOwner && !isInLockedView && hasRemote && !isArchived; final showUnarchive = isOwner && !isInLockedView && hasRemote && isArchived; - final menuItemHeight = 30.0; - final List> items = [ - PopupMenuItem( - enabled: false, - textStyle: context.textTheme.labelMedium, - height: 40, - child: Text("add_to_bottom_bar".tr()), + return [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: Text("add_to_bottom_bar".tr(), style: context.textTheme.labelMedium), ), - PopupMenuItem( - height: menuItemHeight, - value: AddToMenuItem.album, - child: ListTile(leading: const Icon(Icons.photo_album_outlined), title: Text("album".tr())), + BaseActionButton( + iconData: Icons.photo_album_outlined, + label: "album".tr(), + menuItem: true, + onPressed: () => _handleMenuSelection(AddToMenuItem.album), ), - const PopupMenuDivider(), - PopupMenuItem(enabled: false, textStyle: context.textTheme.labelMedium, height: 40, child: Text("move_to".tr())), + if (isOwner) ...[ + const PopupMenuDivider(), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: Text("move_to".tr(), style: context.textTheme.labelMedium), + ), if (showArchive) - PopupMenuItem( - height: menuItemHeight, - value: AddToMenuItem.archive, - child: ListTile(leading: const Icon(Icons.archive_outlined), title: Text("archive".tr())), + BaseActionButton( + iconData: Icons.archive_outlined, + label: "archive".tr(), + menuItem: true, + onPressed: () => _handleMenuSelection(AddToMenuItem.archive), ), if (showUnarchive) - PopupMenuItem( - height: menuItemHeight, - value: AddToMenuItem.unarchive, - child: ListTile(leading: const Icon(Icons.unarchive_outlined), title: Text("unarchive".tr())), + BaseActionButton( + iconData: Icons.unarchive_outlined, + label: "unarchive".tr(), + menuItem: true, + onPressed: () => _handleMenuSelection(AddToMenuItem.unarchive), ), - PopupMenuItem( - height: menuItemHeight, - value: AddToMenuItem.lockedFolder, - child: ListTile(leading: const Icon(Icons.lock_outline), title: Text("locked_folder".tr())), + BaseActionButton( + iconData: Icons.lock_outline, + label: "locked_folder".tr(), + menuItem: true, + onPressed: () => _handleMenuSelection(AddToMenuItem.lockedFolder), ), ], ]; - - final AddToMenuItem? selected = await showMenu( - context: context, - color: context.themeData.scaffoldBackgroundColor, - position: _menuPosition(context), - items: items, - popUpAnimationStyle: AnimationStyle.noAnimation, - ); - - if (selected == null) { - return; - } - - switch (selected) { - case AddToMenuItem.album: - _openAlbumSelector(context, ref); - break; - case AddToMenuItem.archive: - await performArchiveAction(context, ref, source: ActionSource.viewer); - break; - case AddToMenuItem.unarchive: - await performUnArchiveAction(context, ref, source: ActionSource.viewer); - break; - case AddToMenuItem.lockedFolder: - await performMoveToLockFolderAction(context, ref, source: ActionSource.viewer); - break; - } } - RelativeRect _menuPosition(BuildContext context) { - final renderObject = context.findRenderObject(); - if (renderObject is! RenderBox) { - return RelativeRect.fill; - } - - final size = renderObject.size; - final position = renderObject.localToGlobal(Offset.zero); - - return RelativeRect.fromLTRB(position.dx, position.dy - size.height - 200, position.dx + size.width, position.dy); - } - - void _openAlbumSelector(BuildContext context, WidgetRef ref) { + void _openAlbumSelector() { final currentAsset = ref.read(currentAssetNotifier); if (currentAsset == null) { ImmichToast.show(context: context, msg: "Cannot load asset information.", toastType: ToastType.error); return; } - final List slivers = [ - AlbumSelector(onAlbumSelected: (album) => _addCurrentAssetToAlbum(context, ref, album)), - ]; + final List slivers = [AlbumSelector(onAlbumSelected: (album) => _addCurrentAssetToAlbum(album))]; showModalBottomSheet( context: context, @@ -141,7 +127,7 @@ class AddActionButton extends ConsumerWidget { ); } - Future _addCurrentAssetToAlbum(BuildContext context, WidgetRef ref, RemoteAlbum album) async { + Future _addCurrentAssetToAlbum(RemoteAlbum album) async { final latest = ref.read(currentAssetNotifier); if (latest == null) { @@ -174,17 +160,27 @@ class AddActionButton extends ConsumerWidget { } @override - Widget build(BuildContext context, WidgetRef ref) { + Widget build(BuildContext context) { final asset = ref.watch(currentAssetNotifier); if (asset == null) { return const SizedBox.shrink(); } - return Builder( - builder: (buttonContext) { + + return MenuAnchor( + consumeOutsideTap: true, + style: MenuStyle( + backgroundColor: WidgetStatePropertyAll(context.themeData.scaffoldBackgroundColor), + elevation: const WidgetStatePropertyAll(4), + shape: const WidgetStatePropertyAll( + RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(12))), + ), + ), + menuChildren: _buildMenuChildren(), + builder: (context, controller, child) { return BaseActionButton( iconData: Icons.add, label: "add_to_bottom_bar".tr(), - onPressed: () => _showAddOptions(buttonContext, ref), + onPressed: () => controller.isOpen ? controller.close() : controller.open(), ); }, ); diff --git a/mobile/lib/presentation/widgets/action_buttons/base_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/base_action_button.widget.dart index 5ec6c8bc54..e6098b07b4 100644 --- a/mobile/lib/presentation/widgets/action_buttons/base_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/base_action_button.widget.dart @@ -11,6 +11,7 @@ class BaseActionButton extends StatelessWidget { this.onLongPressed, this.maxWidth = 90.0, this.minWidth, + this.iconOnly = false, this.menuItem = false, }); @@ -19,6 +20,11 @@ class BaseActionButton extends StatelessWidget { final Color? iconColor; final double maxWidth; final double? minWidth; + + /// When true, renders only an IconButton without text label + final bool iconOnly; + + /// When true, renders as a MenuItemButton for use in MenuAnchor menus final bool menuItem; final void Function()? onPressed; final void Function()? onLongPressed; @@ -31,13 +37,26 @@ class BaseActionButton extends StatelessWidget { final iconColor = this.iconColor ?? iconTheme.color ?? context.themeData.iconTheme.color; final textColor = context.themeData.textTheme.labelLarge?.color; - if (menuItem) { + if (iconOnly) { return IconButton( onPressed: onPressed, icon: Icon(iconData, size: iconSize, color: iconColor), ); } + if (menuItem) { + final theme = context.themeData; + final effectiveStyle = theme.textTheme.labelLarge; + final effectiveIconColor = iconColor ?? theme.iconTheme.color ?? theme.colorScheme.onSurfaceVariant; + + return MenuItemButton( + style: MenuItemButton.styleFrom(padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12)), + leadingIcon: Icon(iconData, color: effectiveIconColor, size: 20), + onPressed: onPressed, + child: Text(label, style: effectiveStyle), + ); + } + return ConstrainedBox( constraints: BoxConstraints(maxWidth: maxWidth), child: MaterialButton( diff --git a/mobile/lib/presentation/widgets/action_buttons/cast_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/cast_action_button.widget.dart index 26b8ba6f47..2840ad294b 100644 --- a/mobile/lib/presentation/widgets/action_buttons/cast_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/cast_action_button.widget.dart @@ -7,8 +7,9 @@ import 'package:immich_mobile/providers/cast.provider.dart'; import 'package:immich_mobile/widgets/asset_viewer/cast_dialog.dart'; class CastActionButton extends ConsumerWidget { - const CastActionButton({super.key, this.menuItem = true}); + const CastActionButton({super.key, this.iconOnly = true, this.menuItem = false}); + final bool iconOnly; final bool menuItem; @override @@ -22,6 +23,7 @@ class CastActionButton extends ConsumerWidget { onPressed: () { showDialog(context: context, builder: (context) => const CastDialog()); }, + iconOnly: iconOnly, menuItem: menuItem, ); } diff --git a/mobile/lib/presentation/widgets/action_buttons/download_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/download_action_button.widget.dart index cb898f069a..a5129b643a 100644 --- a/mobile/lib/presentation/widgets/action_buttons/download_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/download_action_button.widget.dart @@ -10,8 +10,9 @@ import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; class DownloadActionButton extends ConsumerWidget { final ActionSource source; + final bool iconOnly; final bool menuItem; - const DownloadActionButton({super.key, required this.source, this.menuItem = false}); + const DownloadActionButton({super.key, required this.source, this.iconOnly = false, this.menuItem = false}); void _onTap(BuildContext context, WidgetRef ref, BackgroundSyncManager backgroundSyncManager) async { if (!context.mounted) { @@ -38,6 +39,7 @@ class DownloadActionButton extends ConsumerWidget { iconData: Icons.download, maxWidth: 95, label: "download".t(context: context), + iconOnly: iconOnly, menuItem: menuItem, onPressed: () => _onTap(context, ref, backgroundManager), ); diff --git a/mobile/lib/presentation/widgets/action_buttons/favorite_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/favorite_action_button.widget.dart index 0aca5158ef..ba2491365d 100644 --- a/mobile/lib/presentation/widgets/action_buttons/favorite_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/favorite_action_button.widget.dart @@ -10,9 +10,10 @@ import 'package:immich_mobile/widgets/common/immich_toast.dart'; class FavoriteActionButton extends ConsumerWidget { final ActionSource source; + final bool iconOnly; final bool menuItem; - const FavoriteActionButton({super.key, required this.source, this.menuItem = false}); + const FavoriteActionButton({super.key, required this.source, this.iconOnly = false, this.menuItem = false}); void _onTap(BuildContext context, WidgetRef ref) async { if (!context.mounted) { @@ -44,6 +45,7 @@ class FavoriteActionButton extends ConsumerWidget { return BaseActionButton( iconData: Icons.favorite_border_rounded, label: "favorite".t(context: context), + iconOnly: iconOnly, menuItem: menuItem, onPressed: () => _onTap(context, ref), ); diff --git a/mobile/lib/presentation/widgets/action_buttons/like_activity_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/like_activity_action_button.widget.dart index 33794eae11..a61f72ea01 100644 --- a/mobile/lib/presentation/widgets/action_buttons/like_activity_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/like_activity_action_button.widget.dart @@ -12,8 +12,9 @@ import 'package:immich_mobile/providers/infrastructure/current_album.provider.da import 'package:immich_mobile/providers/user.provider.dart'; class LikeActivityActionButton extends ConsumerWidget { - const LikeActivityActionButton({super.key, this.menuItem = false}); + const LikeActivityActionButton({super.key, this.iconOnly = false, this.menuItem = false}); + final bool iconOnly; final bool menuItem; @override @@ -49,6 +50,7 @@ class LikeActivityActionButton extends ConsumerWidget { iconData: liked != null ? Icons.favorite : Icons.favorite_border, label: "like".t(context: context), onPressed: () => onTap(liked), + iconOnly: iconOnly, menuItem: menuItem, ); }, @@ -57,6 +59,7 @@ class LikeActivityActionButton extends ConsumerWidget { loading: () => BaseActionButton( iconData: Icons.favorite_border, label: "like".t(context: context), + iconOnly: iconOnly, menuItem: menuItem, ), error: (error, stack) => Text('error_saving_image'.tr(args: [error.toString()])), diff --git a/mobile/lib/presentation/widgets/action_buttons/motion_photo_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/motion_photo_action_button.widget.dart index 696b9ff367..9cf541f49f 100644 --- a/mobile/lib/presentation/widgets/action_buttons/motion_photo_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/motion_photo_action_button.widget.dart @@ -5,8 +5,9 @@ import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_bu import 'package:immich_mobile/providers/asset_viewer/is_motion_video_playing.provider.dart'; class MotionPhotoActionButton extends ConsumerWidget { - const MotionPhotoActionButton({super.key, this.menuItem = true}); + const MotionPhotoActionButton({super.key, this.iconOnly = true, this.menuItem = false}); + final bool iconOnly; final bool menuItem; @override @@ -17,6 +18,7 @@ class MotionPhotoActionButton extends ConsumerWidget { iconData: isPlaying ? Icons.motion_photos_pause_outlined : Icons.play_circle_outline_rounded, label: "play_motion_photo".t(context: context), onPressed: ref.read(isPlayingMotionVideoProvider.notifier).toggle, + iconOnly: iconOnly, menuItem: menuItem, ); } diff --git a/mobile/lib/presentation/widgets/action_buttons/unfavorite_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/unfavorite_action_button.widget.dart index 7fdc5e81e8..ec5513e0a8 100644 --- a/mobile/lib/presentation/widgets/action_buttons/unfavorite_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/unfavorite_action_button.widget.dart @@ -10,9 +10,10 @@ import 'package:immich_mobile/widgets/common/immich_toast.dart'; class UnFavoriteActionButton extends ConsumerWidget { final ActionSource source; + final bool iconOnly; final bool menuItem; - const UnFavoriteActionButton({super.key, required this.source, this.menuItem = false}); + const UnFavoriteActionButton({super.key, required this.source, this.iconOnly = false, this.menuItem = false}); void _onTap(BuildContext context, WidgetRef ref) async { if (!context.mounted) { @@ -45,6 +46,7 @@ class UnFavoriteActionButton extends ConsumerWidget { iconData: Icons.favorite_rounded, label: "unfavorite".t(context: context), onPressed: () => _onTap(context, ref), + iconOnly: iconOnly, menuItem: menuItem, ); } diff --git a/mobile/lib/presentation/widgets/asset_viewer/top_app_bar.widget.dart b/mobile/lib/presentation/widgets/asset_viewer/top_app_bar.widget.dart index 5114ef6fd2..b3129a9a0e 100644 --- a/mobile/lib/presentation/widgets/asset_viewer/top_app_bar.widget.dart +++ b/mobile/lib/presentation/widgets/asset_viewer/top_app_bar.widget.dart @@ -14,6 +14,7 @@ import 'package:immich_mobile/presentation/widgets/action_buttons/favorite_actio import 'package:immich_mobile/presentation/widgets/action_buttons/motion_photo_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/unfavorite_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.state.dart'; +import 'package:immich_mobile/presentation/widgets/asset_viewer/viewer_kebab_menu.widget.dart'; import 'package:immich_mobile/providers/activity.provider.dart'; import 'package:immich_mobile/providers/cast.provider.dart'; import 'package:immich_mobile/providers/infrastructure/asset_viewer/current_asset.provider.dart'; @@ -65,8 +66,8 @@ class ViewerTopAppBar extends ConsumerWidget implements PreferredSizeWidget { final isCasting = ref.watch(castProvider.select((c) => c.isCasting)); final actions = [ - if (asset.isRemoteOnly) const DownloadActionButton(source: ActionSource.viewer, menuItem: true), - if (isCasting || (asset.hasRemote)) const CastActionButton(menuItem: true), + if (asset.isRemoteOnly) const DownloadActionButton(source: ActionSource.viewer, iconOnly: true), + if (isCasting || (asset.hasRemote)) const CastActionButton(iconOnly: true), if (album != null && album.isActivityEnabled && album.isShared) IconButton( icon: const Icon(Icons.chat_outlined), @@ -85,16 +86,16 @@ class ViewerTopAppBar extends ConsumerWidget implements PreferredSizeWidget { tooltip: 'view_in_timeline'.t(context: context), ), if (asset.hasRemote && isOwner && !asset.isFavorite) - const FavoriteActionButton(source: ActionSource.viewer, menuItem: true), + const FavoriteActionButton(source: ActionSource.viewer, iconOnly: true), if (asset.hasRemote && isOwner && asset.isFavorite) - const UnFavoriteActionButton(source: ActionSource.viewer, menuItem: true), - if (asset.isMotionPhoto) const MotionPhotoActionButton(menuItem: true), - const _KebabMenu(), + const UnFavoriteActionButton(source: ActionSource.viewer, iconOnly: true), + if (asset.isMotionPhoto) const MotionPhotoActionButton(iconOnly: true), + const ViewerKebabMenu(), ]; final lockedViewActions = [ - if (isCasting || (asset.hasRemote)) const CastActionButton(menuItem: true), - const _KebabMenu(), + if (isCasting || (asset.hasRemote)) const CastActionButton(iconOnly: true), + const ViewerKebabMenu(), ]; return IgnorePointer( @@ -122,20 +123,6 @@ class ViewerTopAppBar extends ConsumerWidget implements PreferredSizeWidget { Size get preferredSize => const Size.fromHeight(60.0); } -class _KebabMenu extends ConsumerWidget { - const _KebabMenu(); - - @override - Widget build(BuildContext context, WidgetRef ref) { - return IconButton( - onPressed: () { - EventStream.shared.emit(const ViewerOpenBottomSheetEvent()); - }, - icon: const Icon(Icons.more_vert_rounded), - ); - } -} - class _AppBarBackButton extends ConsumerWidget { const _AppBarBackButton(); diff --git a/mobile/lib/presentation/widgets/asset_viewer/viewer_kebab_menu.widget.dart b/mobile/lib/presentation/widgets/asset_viewer/viewer_kebab_menu.widget.dart new file mode 100644 index 0000000000..4651b5eea8 --- /dev/null +++ b/mobile/lib/presentation/widgets/asset_viewer/viewer_kebab_menu.widget.dart @@ -0,0 +1,47 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/events.model.dart'; +import 'package:immich_mobile/domain/utils/event_stream.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart'; +import 'package:immich_mobile/providers/infrastructure/asset_viewer/current_asset.provider.dart'; + +class ViewerKebabMenu extends ConsumerWidget { + const ViewerKebabMenu({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final asset = ref.watch(currentAssetNotifier); + if (asset == null) { + return const SizedBox.shrink(); + } + + final menuChildren = [ + BaseActionButton( + label: 'about'.tr(), + iconData: Icons.info_outline, + menuItem: true, + onPressed: () => EventStream.shared.emit(const ViewerOpenBottomSheetEvent()), + ), + ]; + + return MenuAnchor( + consumeOutsideTap: true, + style: MenuStyle( + backgroundColor: WidgetStatePropertyAll(context.themeData.scaffoldBackgroundColor), + elevation: const WidgetStatePropertyAll(4), + shape: const WidgetStatePropertyAll( + RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(12))), + ), + ), + menuChildren: menuChildren, + builder: (context, controller, child) { + return IconButton( + icon: const Icon(Icons.more_vert_rounded), + onPressed: () => controller.isOpen ? controller.close() : controller.open(), + ); + }, + ); + } +} From 1109c3289119b479b6a9f94fc47e8c24050e57da Mon Sep 17 00:00:00 2001 From: Harrison Date: Sat, 6 Dec 2025 16:28:12 +0000 Subject: [PATCH 07/35] fix(docs): websockets in nginx example (#24411) Co-authored-by: Harrison --- docs/docs/administration/reverse-proxy.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/docs/administration/reverse-proxy.md b/docs/docs/administration/reverse-proxy.md index f766f42719..765c889e23 100644 --- a/docs/docs/administration/reverse-proxy.md +++ b/docs/docs/administration/reverse-proxy.md @@ -32,8 +32,6 @@ server { # enable websockets: http://nginx.org/en/docs/http/websocket.html proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; proxy_redirect off; # set timeout @@ -43,6 +41,8 @@ server { location / { proxy_pass http://:2283; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; } # useful when using Let's Encrypt http-01 challenge From 42136f909172d37de4571d96c3b82d05d5798207 Mon Sep 17 00:00:00 2001 From: Sergey Katsubo Date: Sat, 6 Dec 2025 23:45:59 +0300 Subject: [PATCH 08/35] fix(server): update exiftool-vendored to v34 for more robust metadata extraction (#24424) --- e2e/package.json | 2 +- pnpm-lock.yaml | 39 ++++++++++++++++++++------------------- server/package.json | 2 +- 3 files changed, 22 insertions(+), 21 deletions(-) diff --git a/e2e/package.json b/e2e/package.json index e82ca07b78..70669a8546 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -36,7 +36,7 @@ "eslint-config-prettier": "^10.1.8", "eslint-plugin-prettier": "^5.1.3", "eslint-plugin-unicorn": "^62.0.0", - "exiftool-vendored": "^33.0.0", + "exiftool-vendored": "^34.0.0", "globals": "^16.0.0", "jose": "^5.6.3", "luxon": "^3.4.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index db215b6035..09a047b9e0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -244,8 +244,8 @@ importers: specifier: ^62.0.0 version: 62.0.0(eslint@9.39.1(jiti@2.6.1)) exiftool-vendored: - specifier: ^33.0.0 - version: 33.5.0 + specifier: ^34.0.0 + version: 34.0.0 globals: specifier: ^16.0.0 version: 16.5.0 @@ -428,8 +428,8 @@ importers: specifier: 4.3.3 version: 4.3.3 exiftool-vendored: - specifier: ^33.0.0 - version: 33.5.0 + specifier: ^34.0.0 + version: 34.0.0 express: specifier: ^5.1.0 version: 5.2.0 @@ -3236,6 +3236,7 @@ packages: '@koa/router@14.0.0': resolution: {integrity: sha512-LBSu5K0qAaaQcXX/0WIB9PGDevyCxxpnc1uq13vV/CgObaVxuis5hKl3Eboq/8gcb6ebnkAStW9NB/Em2eYyFA==} engines: {node: '>= 20'} + deprecated: Please upgrade to v15 or higher. All reported bugs in this version are fixed in newer releases, dependencies have been updated, and security has been improved. '@koddsson/eslint-plugin-tscompat@0.2.0': resolution: {integrity: sha512-Oqd4kWSX0LiO9wWHjcmDfXZNC7TotFV/tLRhwCFU3XUeb//KYvJ75c9OmeSJ+vBv5lkCeB+xYsqyNrBc5j18XA==} @@ -5503,8 +5504,8 @@ packages: resolution: {integrity: sha512-a28v2eWrrRWPpJSzxc+mKwm0ZtVx/G8SepdQZDArnXYU/XS+IF6mp8aB/4E+hH1tyGCoDo3KlUCdlSxGDsRkAw==} hasBin: true - batch-cluster@15.0.1: - resolution: {integrity: sha512-eUmh0ld1AUPKTEmdzwGF9QTSexXAyt9rA1F5zDfW1wUi3okA3Tal4NLdCeFI6aiKpBenQhR6NmK9bW9tBHTGPQ==} + batch-cluster@16.0.0: + resolution: {integrity: sha512-+T7Ho09ikx/kP4P8M+GEnpuePzRQa4gTUhtPIu6ApFC8+0GY0sri1y1PuB+yfXlQWl5DkHC/e58z3U6g0qCz/A==} engines: {node: '>=20'} batch@0.6.1: @@ -6848,17 +6849,17 @@ packages: resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} engines: {node: '>=10'} - exiftool-vendored.exe@13.42.0: - resolution: {integrity: sha512-6AFybe5IakduMWleuQBfep9OWGSVZSedt2uKL+LzufRsATp+beOF7tZyKtMztjb6VRH1GF/4F9EvBVam6zm70w==} + exiftool-vendored.exe@13.43.0: + resolution: {integrity: sha512-EENHNz86tYY5yHGPtGB2mto3FIGstQvEhrcU34f7fm4RMxBKNfTWYOGkhU1jzvjOi+V4575LQX/FUES1TwgUbQ==} os: [win32] - exiftool-vendored.pl@13.42.0: - resolution: {integrity: sha512-EF5IdxQNIJIvZjHf4bG4jnwAHVVSLkYZToo2q+Mm89kSuppKfRvHz/lngIxN0JALE8rFdC4zt6NWY/PKqRdCcg==} + exiftool-vendored.pl@13.43.0: + resolution: {integrity: sha512-0ApWaQ/pxaliPK7HzTxVA0sg/wZ8vl7UtFVhCyWhGQg01WfZkFrKwKmELB0Bnn01WTfgIuMadba8ccmFvpmJag==} os: ['!win32'] hasBin: true - exiftool-vendored@33.5.0: - resolution: {integrity: sha512-7cCh6izwdmC5ZaCxpHFehnExIr2Yp7CJuxHg4WFiGcm81yyxXLtvSE+85ep9VsNwhlOtSpk+XxiqrlddjY5lAw==} + exiftool-vendored@34.0.0: + resolution: {integrity: sha512-rhIe4XGE7kh76nwytwHtq6qK/pc1mpOBHRV++gudFeG2PfAp3XIVQbFWCLK3S4l9I4AWYOe4mxk8mW8l1oHRTw==} engines: {node: '>=20.0.0'} expect-type@1.2.1: @@ -17580,7 +17581,7 @@ snapshots: baseline-browser-mapping@2.8.31: {} - batch-cluster@15.0.1: {} + batch-cluster@16.0.0: {} batch@0.6.1: {} @@ -19128,21 +19129,21 @@ snapshots: signal-exit: 3.0.7 strip-final-newline: 2.0.0 - exiftool-vendored.exe@13.42.0: + exiftool-vendored.exe@13.43.0: optional: true - exiftool-vendored.pl@13.42.0: {} + exiftool-vendored.pl@13.43.0: {} - exiftool-vendored@33.5.0: + exiftool-vendored@34.0.0: dependencies: '@photostructure/tz-lookup': 11.3.0 '@types/luxon': 3.7.1 - batch-cluster: 15.0.1 - exiftool-vendored.pl: 13.42.0 + batch-cluster: 16.0.0 + exiftool-vendored.pl: 13.43.0 he: 1.2.0 luxon: 3.7.2 optionalDependencies: - exiftool-vendored.exe: 13.42.0 + exiftool-vendored.exe: 13.43.0 expect-type@1.2.1: {} diff --git a/server/package.json b/server/package.json index 617e52cd14..d4efb4b1bb 100644 --- a/server/package.json +++ b/server/package.json @@ -70,7 +70,7 @@ "cookie": "^1.0.2", "cookie-parser": "^1.4.7", "cron": "4.3.3", - "exiftool-vendored": "^33.0.0", + "exiftool-vendored": "^34.0.0", "express": "^5.1.0", "fast-glob": "^3.3.2", "fluent-ffmpeg": "^2.1.2", From 879e0ea131b1b8ba8ece2f94f0118f44ae7d1440 Mon Sep 17 00:00:00 2001 From: Min Idzelis Date: Sat, 6 Dec 2025 15:52:06 -0500 Subject: [PATCH 09/35] fix: thumbnail doesnt send mouseLeave events properly (#24423) --- web/src/lib/components/assets/thumbnail/thumbnail.svelte | 1 + 1 file changed, 1 insertion(+) diff --git a/web/src/lib/components/assets/thumbnail/thumbnail.svelte b/web/src/lib/components/assets/thumbnail/thumbnail.svelte index 38d734fc22..63e6f7cc04 100644 --- a/web/src/lib/components/assets/thumbnail/thumbnail.svelte +++ b/web/src/lib/components/assets/thumbnail/thumbnail.svelte @@ -126,6 +126,7 @@ const onMouseLeave = () => { mouseOver = false; + onMouseEvent?.({ isMouseOver: false, selectedGroupIndex: groupIndex }); }; let timer: ReturnType | null = null; From 1e1cf0d1feb914fb260b473c862d38127440d0ff Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 6 Dec 2025 14:55:53 -0600 Subject: [PATCH 10/35] fix: build iOS fastlane installation (#24408) --- .github/workflows/build-mobile.yml | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/.github/workflows/build-mobile.yml b/.github/workflows/build-mobile.yml index 9495d03bb9..b49f63fc68 100644 --- a/.github/workflows/build-mobile.yml +++ b/.github/workflows/build-mobile.yml @@ -222,6 +222,7 @@ jobs: uses: ruby/setup-ruby@v1 with: ruby-version: '3.3' + bundler-cache: true working-directory: ./mobile/ios - name: Install CocoaPods dependencies @@ -229,13 +230,6 @@ jobs: run: | pod install - - name: Install Fastlane - working-directory: ./mobile/ios - run: | - gem install bundler - bundle config set --local path 'vendor/bundle' - bundle install - - name: Create API Key env: API_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }} From 19958dfd836cd829861155a672f41a541d5fdd58 Mon Sep 17 00:00:00 2001 From: Sergey Katsubo Date: Mon, 8 Dec 2025 18:15:43 +0300 Subject: [PATCH 11/35] fix(server): building docker image for different platforms on the same host (#24459) Fix building docker image for different platforms on the same host Use per-platform mise cache to avoid 'sh: 1: extism-js: not found' This happens due to re-using cached installed binary for another platform --- server/Dockerfile | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/server/Dockerfile b/server/Dockerfile index 267253ccd9..918658e19f 100644 --- a/server/Dockerfile +++ b/server/Dockerfile @@ -50,13 +50,15 @@ RUN --mount=type=cache,id=pnpm-cli,target=/buildcache/pnpm-store \ FROM builder AS plugins +ARG TARGETPLATFORM + COPY --from=ghcr.io/jdx/mise:2025.11.3@sha256:ac26f5978c0e2783f3e68e58ce75eddb83e41b89bf8747c503bac2aa9baf22c5 /usr/local/bin/mise /usr/local/bin/mise WORKDIR /usr/src/app COPY ./plugins/mise.toml ./plugins/ ENV MISE_TRUSTED_CONFIG_PATHS=/usr/src/app/plugins/mise.toml ENV MISE_DATA_DIR=/buildcache/mise -RUN --mount=type=cache,id=mise-tools,target=/buildcache/mise \ +RUN --mount=type=cache,id=mise-tools-${TARGETPLATFORM},target=/buildcache/mise \ mise install --cd plugins COPY ./plugins ./plugins/ @@ -66,7 +68,7 @@ RUN --mount=type=cache,id=pnpm-plugins,target=/buildcache/pnpm-store \ --mount=type=bind,source=.pnpmfile.cjs,target=.pnpmfile.cjs \ --mount=type=bind,source=pnpm-lock.yaml,target=pnpm-lock.yaml \ --mount=type=bind,source=pnpm-workspace.yaml,target=pnpm-workspace.yaml \ - --mount=type=cache,id=mise-tools,target=/buildcache/mise \ + --mount=type=cache,id=mise-tools-${TARGETPLATFORM},target=/buildcache/mise \ cd plugins && mise run build FROM ghcr.io/immich-app/base-server-prod:202511261514@sha256:c04c1c38dd90e53455b180aedf93c3c63474c8d20ffe2c6d7a3a61a2181e6d29 From 8b31936bb66ae85bf83a2d6afb5a1bd2de798dbf Mon Sep 17 00:00:00 2001 From: Yaros Date: Mon, 8 Dec 2025 16:33:01 +0100 Subject: [PATCH 12/35] fix(mobile): cannot create album while name field is focused (#24449) fix(mobile): create album disabled when focused --- .../presentation/pages/drift_create_album.page.dart | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/mobile/lib/presentation/pages/drift_create_album.page.dart b/mobile/lib/presentation/pages/drift_create_album.page.dart index 57e5cb09a9..f1cbdb13ff 100644 --- a/mobile/lib/presentation/pages/drift_create_album.page.dart +++ b/mobile/lib/presentation/pages/drift_create_album.page.dart @@ -27,8 +27,19 @@ class _DriftCreateAlbumPageState extends ConsumerState { bool isAlbumTitleTextFieldFocus = false; Set selectedAssets = {}; + @override + void initState() { + super.initState(); + albumTitleController.addListener(_onTitleChanged); + } + + void _onTitleChanged() { + setState(() {}); + } + @override void dispose() { + albumTitleController.removeListener(_onTitleChanged); albumTitleController.dispose(); albumDescriptionController.dispose(); albumTitleTextFieldFocusNode.dispose(); From fe9125a3d11b6630296227d3350479b4e671be34 Mon Sep 17 00:00:00 2001 From: Simon Kubiak Date: Mon, 8 Dec 2025 15:35:58 +0000 Subject: [PATCH 13/35] fix(web): [album table view] long album title overflows table row (#24450) fix(web): long album title overflows vertically on album page in table view --- web/src/lib/components/album-page/albums-table-row.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/lib/components/album-page/albums-table-row.svelte b/web/src/lib/components/album-page/albums-table-row.svelte index 865eac8366..852bbf166c 100644 --- a/web/src/lib/components/album-page/albums-table-row.svelte +++ b/web/src/lib/components/album-page/albums-table-row.svelte @@ -32,7 +32,7 @@ goto(resolve(`${AppRoute.ALBUMS}/${album.id}`))} {oncontextmenu} > From 287f6d5c9494f71bd88bc39f815c1331504844a1 Mon Sep 17 00:00:00 2001 From: idubnori Date: Tue, 9 Dec 2025 05:29:31 +0900 Subject: [PATCH 14/35] fix(mobile): buttons inside AddActionButton color is the same as background color (#24460) * fix: icon & text color in AddActionButton * fix: use Divider --- .../add_action_button.widget.dart | 19 +++++++++++++++---- .../asset_viewer/bottom_bar.widget.dart | 4 +++- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/mobile/lib/presentation/widgets/action_buttons/add_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/add_action_button.widget.dart index 054f058739..acd7ede6dc 100644 --- a/mobile/lib/presentation/widgets/action_buttons/add_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/add_action_button.widget.dart @@ -22,7 +22,9 @@ import 'package:immich_mobile/presentation/widgets/bottom_sheet/base_bottom_shee enum AddToMenuItem { album, archive, unarchive, lockedFolder } class AddActionButton extends ConsumerStatefulWidget { - const AddActionButton({super.key}); + const AddActionButton({super.key, this.originalTheme}); + + final ThemeData? originalTheme; @override ConsumerState createState() => _AddActionButtonState(); @@ -71,7 +73,7 @@ class _AddActionButtonState extends ConsumerState { ), if (isOwner) ...[ - const PopupMenuDivider(), + const Divider(), Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: Text("move_to".tr(), style: context.textTheme.labelMedium), @@ -166,16 +168,25 @@ class _AddActionButtonState extends ConsumerState { return const SizedBox.shrink(); } + final themeData = widget.originalTheme ?? context.themeData; + return MenuAnchor( consumeOutsideTap: true, style: MenuStyle( - backgroundColor: WidgetStatePropertyAll(context.themeData.scaffoldBackgroundColor), + backgroundColor: WidgetStatePropertyAll(themeData.scaffoldBackgroundColor), elevation: const WidgetStatePropertyAll(4), shape: const WidgetStatePropertyAll( RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(12))), ), ), - menuChildren: _buildMenuChildren(), + menuChildren: widget.originalTheme != null + ? [ + Theme( + data: widget.originalTheme!, + child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: _buildMenuChildren()), + ), + ] + : _buildMenuChildren(), builder: (context, controller, child) { return BaseActionButton( iconData: Icons.add, diff --git a/mobile/lib/presentation/widgets/asset_viewer/bottom_bar.widget.dart b/mobile/lib/presentation/widgets/asset_viewer/bottom_bar.widget.dart index 14c03ad637..67bbc4c83a 100644 --- a/mobile/lib/presentation/widgets/asset_viewer/bottom_bar.widget.dart +++ b/mobile/lib/presentation/widgets/asset_viewer/bottom_bar.widget.dart @@ -38,11 +38,13 @@ class ViewerBottomBar extends ConsumerWidget { opacity = 0; } + final originalTheme = context.themeData; + final actions = [ const ShareActionButton(source: ActionSource.viewer), if (asset.isLocalOnly) const UploadActionButton(source: ActionSource.viewer), if (asset.type == AssetType.image) const EditImageActionButton(), - if (asset.hasRemote) const AddActionButton(), + if (asset.hasRemote) AddActionButton(originalTheme: originalTheme), if (isOwner) ...[ asset.isLocalOnly From c360781565d0f14d07a590ea302753e3732b84b9 Mon Sep 17 00:00:00 2001 From: Yaros Date: Tue, 9 Dec 2025 16:03:29 +0100 Subject: [PATCH 15/35] fix(mobile): fix overflow text in backup card (#24448) * fix(mobile): fix overflow text in backup card * refactor: use intrinsicheight * chore: fix spelling of entitycounttile --- .../beta_sync_settings/entity_count_tile.dart | 57 +++---- .../sync_status_and_actions.dart | 158 ++++++++++-------- 2 files changed, 113 insertions(+), 102 deletions(-) diff --git a/mobile/lib/widgets/settings/beta_sync_settings/entity_count_tile.dart b/mobile/lib/widgets/settings/beta_sync_settings/entity_count_tile.dart index ac357c2dee..d9a0bae606 100644 --- a/mobile/lib/widgets/settings/beta_sync_settings/entity_count_tile.dart +++ b/mobile/lib/widgets/settings/beta_sync_settings/entity_count_tile.dart @@ -2,26 +2,27 @@ import 'package:flutter/material.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/theme_extensions.dart'; -class EntitiyCountTile extends StatelessWidget { +class EntityCountTile extends StatelessWidget { final int count; final String label; final IconData icon; - const EntitiyCountTile({super.key, required this.count, required this.label, required this.icon}); + const EntityCountTile({super.key, required this.count, required this.label, required this.icon}); String zeroPadding(int number, int targetWidth) { final numStr = number.toString(); return numStr.length < targetWidth ? "0" * (targetWidth - numStr.length) : ""; } - int calculateMaxDigits(double availableWidth) { - const double charWidth = 11.0; - return (availableWidth / charWidth).floor().clamp(1, 8); - } - @override Widget build(BuildContext context) { + final screenWidth = MediaQuery.of(context).size.width; + final availableWidth = (screenWidth - 32 - 8) / 2; + const double charWidth = 11.0; + final maxDigits = ((availableWidth - 32) / charWidth).floor().clamp(1, 8); + return Container( + height: double.infinity, padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: context.colorScheme.surfaceContainerLow, @@ -29,7 +30,6 @@ class EntitiyCountTile extends StatelessWidget { border: Border.all(width: 0.5, color: context.colorScheme.outline.withAlpha(25)), ), child: Column( - mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.center, children: [ // Icon and Label @@ -38,33 +38,30 @@ class EntitiyCountTile extends StatelessWidget { children: [ Icon(icon, color: context.primaryColor), const SizedBox(width: 8), - Text( - label, - style: TextStyle(color: context.primaryColor, fontWeight: FontWeight.bold, fontSize: 16), + Flexible( + child: Text( + label, + style: TextStyle(color: context.primaryColor, fontWeight: FontWeight.bold, fontSize: 16), + ), ), ], ), - const SizedBox(height: 12), // Number - LayoutBuilder( - builder: (context, constraints) { - final maxDigits = calculateMaxDigits(constraints.maxWidth); - return RichText( - text: TextSpan( - style: const TextStyle(fontSize: 18, fontFamily: 'OverpassMono', fontWeight: FontWeight.w600), - children: [ - TextSpan( - text: zeroPadding(count, maxDigits), - style: TextStyle(color: context.colorScheme.onSurfaceSecondary.withAlpha(75)), - ), - TextSpan( - text: count.toString(), - style: TextStyle(color: context.primaryColor), - ), - ], + const Spacer(), + RichText( + text: TextSpan( + style: const TextStyle(fontSize: 18, fontFamily: 'OverpassMono', fontWeight: FontWeight.w600), + children: [ + TextSpan( + text: zeroPadding(count, maxDigits), + style: TextStyle(color: context.colorScheme.onSurfaceSecondary.withAlpha(75)), ), - ); - }, + TextSpan( + text: count.toString(), + style: TextStyle(color: context.primaryColor), + ), + ], + ), ), ], ), diff --git a/mobile/lib/widgets/settings/beta_sync_settings/sync_status_and_actions.dart b/mobile/lib/widgets/settings/beta_sync_settings/sync_status_and_actions.dart index 64c3d9b832..d4730951c0 100644 --- a/mobile/lib/widgets/settings/beta_sync_settings/sync_status_and_actions.dart +++ b/mobile/lib/widgets/settings/beta_sync_settings/sync_status_and_actions.dart @@ -282,76 +282,87 @@ class _SyncStatsCounts extends ConsumerWidget { _SectionHeaderText(text: "assets".t(context: context)), Padding( padding: const EdgeInsets.fromLTRB(16, 8, 16, 16), - child: Flex( - direction: Axis.horizontal, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - spacing: 8.0, - children: [ - Expanded( - child: EntitiyCountTile( - label: "local".t(context: context), - count: localAssetCount, - icon: Icons.smartphone, + // 1. Wrap in IntrinsicHeight + child: IntrinsicHeight( + child: Flex( + direction: Axis.horizontal, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + // 2. Stretch children vertically to fill the IntrinsicHeight + crossAxisAlignment: CrossAxisAlignment.stretch, + spacing: 8.0, + children: [ + Expanded( + child: EntityCountTile( + label: "local".t(context: context), + count: localAssetCount, + icon: Icons.smartphone, + ), ), - ), - Expanded( - child: EntitiyCountTile( - label: "remote".t(context: context), - count: remoteAssetCount, - icon: Icons.cloud, + Expanded( + child: EntityCountTile( + label: "remote".t(context: context), + count: remoteAssetCount, + icon: Icons.cloud, + ), ), - ), - ], + ], + ), ), ), _SectionHeaderText(text: "albums".t(context: context)), Padding( padding: const EdgeInsets.fromLTRB(16, 8, 16, 16), - child: Flex( - direction: Axis.horizontal, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - spacing: 8.0, - children: [ - Expanded( - child: EntitiyCountTile( - label: "local".t(context: context), - count: localAlbumCount, - icon: Icons.smartphone, + child: IntrinsicHeight( + child: Flex( + direction: Axis.horizontal, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.stretch, // Added + spacing: 8.0, + children: [ + Expanded( + child: EntityCountTile( + label: "local".t(context: context), + count: localAlbumCount, + icon: Icons.smartphone, + ), ), - ), - Expanded( - child: EntitiyCountTile( - label: "remote".t(context: context), - count: remoteAlbumCount, - icon: Icons.cloud, + Expanded( + child: EntityCountTile( + label: "remote".t(context: context), + count: remoteAlbumCount, + icon: Icons.cloud, + ), ), - ), - ], + ], + ), ), ), _SectionHeaderText(text: "other".t(context: context)), Padding( padding: const EdgeInsets.fromLTRB(16, 8, 16, 16), - child: Flex( - direction: Axis.horizontal, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - spacing: 8.0, - children: [ - Expanded( - child: EntitiyCountTile( - label: "memories".t(context: context), - count: memoryCount, - icon: Icons.calendar_today, + child: IntrinsicHeight( + child: Flex( + direction: Axis.horizontal, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.stretch, // Added + spacing: 8.0, + children: [ + Expanded( + child: EntityCountTile( + label: "memories".t(context: context), + count: memoryCount, + icon: Icons.calendar_today, + ), ), - ), - Expanded( - child: EntitiyCountTile( - label: "hashed_assets".t(context: context), - count: localHashedCount, - icon: Icons.tag, + Expanded( + child: EntityCountTile( + label: "hashed_assets".t(context: context), + count: localHashedCount, + icon: Icons.tag, + ), ), - ), - ], + ], + ), ), ), // To be removed once the experimental feature is stable @@ -364,26 +375,29 @@ class _SyncStatsCounts extends ConsumerWidget { return counts.when( data: (c) => Padding( padding: const EdgeInsets.fromLTRB(16, 8, 16, 16), - child: Flex( - direction: Axis.horizontal, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - spacing: 8.0, - children: [ - Expanded( - child: EntitiyCountTile( - label: "local".t(context: context), - count: c.total, - icon: Icons.delete_outline, + child: IntrinsicHeight( + child: Flex( + direction: Axis.horizontal, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.stretch, // Added + spacing: 8.0, + children: [ + Expanded( + child: EntityCountTile( + label: "local".t(context: context), + count: c.total, + icon: Icons.delete_outline, + ), ), - ), - Expanded( - child: EntitiyCountTile( - label: "hashed_assets".t(context: context), - count: c.hashed, - icon: Icons.tag, + Expanded( + child: EntityCountTile( + label: "hashed_assets".t(context: context), + count: c.hashed, + icon: Icons.tag, + ), ), - ), - ], + ], + ), ), ), loading: () => const CircularProgressIndicator(), From 06e79703da1dc1019e97fbc6904f655f9d3bf533 Mon Sep 17 00:00:00 2001 From: Yaros Date: Tue, 9 Dec 2025 16:19:41 +0100 Subject: [PATCH 16/35] fix(mobile): timeline bottom padding on selection (#24480) --- .../presentation/widgets/timeline/timeline.widget.dart | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/mobile/lib/presentation/widgets/timeline/timeline.widget.dart b/mobile/lib/presentation/widgets/timeline/timeline.widget.dart index 5868de92aa..a04e26d653 100644 --- a/mobile/lib/presentation/widgets/timeline/timeline.widget.dart +++ b/mobile/lib/presentation/widgets/timeline/timeline.widget.dart @@ -324,7 +324,11 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> { final topPadding = context.padding.top + (widget.appBar == null ? 0 : kToolbarHeight) + 10; const scrubberBottomPadding = 100.0; - final bottomPadding = context.padding.bottom + (widget.appBar == null ? 0 : scrubberBottomPadding); + const bottomSheetOpenModifier = 120.0; + final bottomPadding = + context.padding.bottom + + (widget.appBar == null ? 0 : scrubberBottomPadding) + + (isMultiSelectEnabled ? bottomSheetOpenModifier : 0); final grid = CustomScrollView( primary: true, @@ -347,7 +351,7 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> { addRepaintBoundaries: false, ), ), - const SliverPadding(padding: EdgeInsets.only(bottom: scrubberBottomPadding)), + SliverPadding(padding: EdgeInsets.only(bottom: bottomPadding)), ], ); From 01e39277e029aed22fc3388d0d0466555b8f4a1b Mon Sep 17 00:00:00 2001 From: Arnau Mora Date: Tue, 9 Dec 2025 17:23:01 +0000 Subject: [PATCH 17/35] feat(mobile): Localized backup upload details page (#21136) * Localized backup details page # Conflicts: # i18n/en.json * Format * format fix --------- Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com> --- i18n/en.json | 5 ++++ .../backup/drift_upload_detail.page.dart | 24 ++++++++++++------- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/i18n/en.json b/i18n/en.json index 7eb9ffbef6..5903d7850e 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -652,6 +652,7 @@ "backup_options_page_title": "Backup options", "backup_setting_subtitle": "Manage background and foreground upload settings", "backup_settings_subtitle": "Manage upload settings", + "backup_upload_details_page_more_details": "Tap for more details", "backward": "Backward", "biometric_auth_enabled": "Biometric authentication enabled", "biometric_locked_out": "You are locked out of biometric authentication", @@ -718,6 +719,7 @@ "check_corrupt_asset_backup_button": "Perform check", "check_corrupt_asset_backup_description": "Run this check only over Wi-Fi and once all assets have been backed-up. The procedure might take a few minutes.", "check_logs": "Check Logs", + "checksum": "Checksum", "choose_matching_people_to_merge": "Choose matching people to merge", "city": "City", "clear": "Clear", @@ -1166,6 +1168,7 @@ "header_settings_header_name_input": "Header name", "header_settings_header_value_input": "Header value", "headers_settings_tile_title": "Custom proxy headers", + "height": "Height", "hi_user": "Hi {name} ({email})", "hide_all_people": "Hide all people", "hide_gallery": "Hide gallery", @@ -1288,6 +1291,7 @@ "local": "Local", "local_asset_cast_failed": "Unable to cast an asset that is not uploaded to the server", "local_assets": "Local Assets", + "local_id": "Local ID", "local_media_summary": "Local Media Summary", "local_network": "Local network", "local_network_sheet_info": "The app will connect to the server through this URL when using the specified Wi-Fi network", @@ -2218,6 +2222,7 @@ "week": "Week", "welcome": "Welcome", "welcome_to_immich": "Welcome to Immich", + "width": "Width", "wifi_name": "Wi-Fi Name", "workflow": "Workflow", "wrong_pin_code": "Wrong PIN code", diff --git a/mobile/lib/pages/backup/drift_upload_detail.page.dart b/mobile/lib/pages/backup/drift_upload_detail.page.dart index 1b8aa57eaa..612b6a8111 100644 --- a/mobile/lib/pages/backup/drift_upload_detail.page.dart +++ b/mobile/lib/pages/backup/drift_upload_detail.page.dart @@ -98,7 +98,7 @@ class DriftUploadDetailPage extends ConsumerWidget { ), ), Text( - 'Tap for more details', + "backup_upload_details_page_more_details".t(context: context), style: context.textTheme.bodySmall?.copyWith( color: context.colorScheme.onSurface.withValues(alpha: 0.6), ), @@ -239,14 +239,20 @@ class FileDetailDialog extends ConsumerWidget { const SizedBox(height: 24), if (asset != null) ...[ _buildInfoSection(context, [ - _buildInfoRow(context, "Filename", path.basename(uploadStatus.filename)), - _buildInfoRow(context, "Local ID", asset.id), - _buildInfoRow(context, "File Size", formatHumanReadableBytes(uploadStatus.fileSize, 2)), - if (asset.width != null) _buildInfoRow(context, "Width", "${asset.width}px"), - if (asset.height != null) _buildInfoRow(context, "Height", "${asset.height}px"), - _buildInfoRow(context, "Created At", asset.createdAt.toString()), - _buildInfoRow(context, "Updated At", asset.updatedAt.toString()), - if (asset.checksum != null) _buildInfoRow(context, "Checksum", asset.checksum!), + _buildInfoRow(context, "filename".t(context: context), path.basename(uploadStatus.filename)), + _buildInfoRow(context, "local_id".t(context: context), asset.id), + _buildInfoRow( + context, + "file_size".t(context: context), + formatHumanReadableBytes(uploadStatus.fileSize, 2), + ), + if (asset.width != null) _buildInfoRow(context, "width".t(context: context), "${asset.width}px"), + if (asset.height != null) + _buildInfoRow(context, "height".t(context: context), "${asset.height}px"), + _buildInfoRow(context, "created_at".t(context: context), asset.createdAt.toString()), + _buildInfoRow(context, "updated_at".t(context: context), asset.updatedAt.toString()), + if (asset.checksum != null) + _buildInfoRow(context, "checksum".t(context: context), asset.checksum!), ]), ], ], From 7af99b86068bd4407a1c0acab9117741e7846fbb Mon Sep 17 00:00:00 2001 From: idubnori Date: Wed, 10 Dec 2025 03:26:28 +0900 Subject: [PATCH 18/35] feat(mobile): move top bar buttons into kebabu menu in AssetViewer (#24461) * chore(mobile): i18n: "open_asset_info" in viewer kebab menu * feat(mobile): move some top buttons into kebabu menu * refactor(mobile): viewer kebab menu to use context-based button generation * feat(mobile): refactor action button and kebab menu to use ConsumerWidget for improved state management * feat(mobile): pass original theme to ViewerKebabMenu for consistent styling * chore: styling --------- Co-authored-by: Alex --- .../add_action_button.widget.dart | 2 + .../base_action_button.widget.dart | 12 +- .../cast_action_button.widget.dart | 2 +- .../motion_photo_action_button.widget.dart | 2 +- .../asset_viewer/top_app_bar.widget.dart | 41 +------ .../viewer_kebab_menu.widget.dart | 48 +++++--- mobile/lib/utils/action_button.utils.dart | 107 +++++++++++++++++- 7 files changed, 156 insertions(+), 58 deletions(-) diff --git a/mobile/lib/presentation/widgets/action_buttons/add_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/add_action_button.widget.dart index acd7ede6dc..08ac9f982c 100644 --- a/mobile/lib/presentation/widgets/action_buttons/add_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/add_action_button.widget.dart @@ -174,10 +174,12 @@ class _AddActionButtonState extends ConsumerState { consumeOutsideTap: true, style: MenuStyle( backgroundColor: WidgetStatePropertyAll(themeData.scaffoldBackgroundColor), + surfaceTintColor: const WidgetStatePropertyAll(Colors.grey), elevation: const WidgetStatePropertyAll(4), shape: const WidgetStatePropertyAll( RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(12))), ), + padding: const WidgetStatePropertyAll(EdgeInsets.symmetric(vertical: 6)), ), menuChildren: widget.originalTheme != null ? [ diff --git a/mobile/lib/presentation/widgets/action_buttons/base_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/base_action_button.widget.dart index e6098b07b4..675b5bf219 100644 --- a/mobile/lib/presentation/widgets/action_buttons/base_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/base_action_button.widget.dart @@ -1,7 +1,8 @@ import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -class BaseActionButton extends StatelessWidget { +class BaseActionButton extends ConsumerWidget { const BaseActionButton({ super.key, required this.label, @@ -30,7 +31,7 @@ class BaseActionButton extends StatelessWidget { final void Function()? onLongPressed; @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { final miniWidth = minWidth ?? (context.isMobile ? context.width / 4.5 : 75.0); final iconTheme = IconTheme.of(context); final iconSize = iconTheme.size ?? 24.0; @@ -46,14 +47,13 @@ class BaseActionButton extends StatelessWidget { if (menuItem) { final theme = context.themeData; - final effectiveStyle = theme.textTheme.labelLarge; final effectiveIconColor = iconColor ?? theme.iconTheme.color ?? theme.colorScheme.onSurfaceVariant; return MenuItemButton( - style: MenuItemButton.styleFrom(padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12)), - leadingIcon: Icon(iconData, color: effectiveIconColor, size: 20), + style: MenuItemButton.styleFrom(alignment: Alignment.centerLeft, padding: const EdgeInsets.all(16)), + leadingIcon: Icon(iconData, color: effectiveIconColor), onPressed: onPressed, - child: Text(label, style: effectiveStyle), + child: Text(label, style: theme.textTheme.labelLarge?.copyWith(fontSize: 16)), ); } diff --git a/mobile/lib/presentation/widgets/action_buttons/cast_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/cast_action_button.widget.dart index 2840ad294b..7a4f84fb4f 100644 --- a/mobile/lib/presentation/widgets/action_buttons/cast_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/cast_action_button.widget.dart @@ -7,7 +7,7 @@ import 'package:immich_mobile/providers/cast.provider.dart'; import 'package:immich_mobile/widgets/asset_viewer/cast_dialog.dart'; class CastActionButton extends ConsumerWidget { - const CastActionButton({super.key, this.iconOnly = true, this.menuItem = false}); + const CastActionButton({super.key, this.iconOnly = false, this.menuItem = false}); final bool iconOnly; final bool menuItem; diff --git a/mobile/lib/presentation/widgets/action_buttons/motion_photo_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/motion_photo_action_button.widget.dart index 9cf541f49f..3bd67978e2 100644 --- a/mobile/lib/presentation/widgets/action_buttons/motion_photo_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/motion_photo_action_button.widget.dart @@ -5,7 +5,7 @@ import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_bu import 'package:immich_mobile/providers/asset_viewer/is_motion_video_playing.provider.dart'; class MotionPhotoActionButton extends ConsumerWidget { - const MotionPhotoActionButton({super.key, this.iconOnly = true, this.menuItem = false}); + const MotionPhotoActionButton({super.key, this.iconOnly = false, this.menuItem = false}); final bool iconOnly; final bool menuItem; diff --git a/mobile/lib/presentation/widgets/asset_viewer/top_app_bar.widget.dart b/mobile/lib/presentation/widgets/asset_viewer/top_app_bar.widget.dart index b3129a9a0e..193cf60220 100644 --- a/mobile/lib/presentation/widgets/asset_viewer/top_app_bar.widget.dart +++ b/mobile/lib/presentation/widgets/asset_viewer/top_app_bar.widget.dart @@ -4,26 +4,19 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/enums.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/models/events.model.dart'; -import 'package:immich_mobile/domain/services/timeline.service.dart'; import 'package:immich_mobile/domain/utils/event_stream.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/extensions/translate_extensions.dart'; -import 'package:immich_mobile/presentation/widgets/action_buttons/cast_action_button.widget.dart'; -import 'package:immich_mobile/presentation/widgets/action_buttons/download_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/favorite_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/motion_photo_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/unfavorite_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.state.dart'; import 'package:immich_mobile/presentation/widgets/asset_viewer/viewer_kebab_menu.widget.dart'; import 'package:immich_mobile/providers/activity.provider.dart'; -import 'package:immich_mobile/providers/cast.provider.dart'; import 'package:immich_mobile/providers/infrastructure/asset_viewer/current_asset.provider.dart'; import 'package:immich_mobile/providers/infrastructure/current_album.provider.dart'; import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; import 'package:immich_mobile/providers/routes.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; -import 'package:immich_mobile/routing/router.dart'; class ViewerTopAppBar extends ConsumerWidget implements PreferredSizeWidget { const ViewerTopAppBar({super.key}); @@ -42,15 +35,6 @@ class ViewerTopAppBar extends ConsumerWidget implements PreferredSizeWidget { final isInLockedView = ref.watch(inLockedViewProvider); final isReadonlyModeEnabled = ref.watch(readonlyModeProvider); - final timelineOrigin = ref.read(timelineServiceProvider).origin; - final showViewInTimelineButton = - timelineOrigin != TimelineOrigin.main && - timelineOrigin != TimelineOrigin.deepLink && - timelineOrigin != TimelineOrigin.trash && - timelineOrigin != TimelineOrigin.archive && - timelineOrigin != TimelineOrigin.localAlbum && - isOwner; - final isShowingSheet = ref.watch(assetViewerProvider.select((state) => state.showingBottomSheet)); int opacity = ref.watch(assetViewerProvider.select((state) => state.backgroundOpacity)); final showControls = ref.watch(assetViewerProvider.select((s) => s.showingControls)); @@ -63,11 +47,10 @@ class ViewerTopAppBar extends ConsumerWidget implements PreferredSizeWidget { opacity = 0; } - final isCasting = ref.watch(castProvider.select((c) => c.isCasting)); + final originalTheme = context.themeData; final actions = [ - if (asset.isRemoteOnly) const DownloadActionButton(source: ActionSource.viewer, iconOnly: true), - if (isCasting || (asset.hasRemote)) const CastActionButton(iconOnly: true), + if (asset.isMotionPhoto) const MotionPhotoActionButton(iconOnly: true), if (album != null && album.isActivityEnabled && album.isShared) IconButton( icon: const Icon(Icons.chat_outlined), @@ -75,28 +58,16 @@ class ViewerTopAppBar extends ConsumerWidget implements PreferredSizeWidget { EventStream.shared.emit(const ViewerOpenBottomSheetEvent(activitiesMode: true)); }, ), - if (showViewInTimelineButton) - IconButton( - onPressed: () async { - await context.maybePop(); - await context.navigateTo(const TabShellRoute(children: [MainTimelineRoute()])); - EventStream.shared.emit(ScrollToDateEvent(asset.createdAt)); - }, - icon: const Icon(Icons.image_search), - tooltip: 'view_in_timeline'.t(context: context), - ), + if (asset.hasRemote && isOwner && !asset.isFavorite) const FavoriteActionButton(source: ActionSource.viewer, iconOnly: true), if (asset.hasRemote && isOwner && asset.isFavorite) const UnFavoriteActionButton(source: ActionSource.viewer, iconOnly: true), - if (asset.isMotionPhoto) const MotionPhotoActionButton(iconOnly: true), - const ViewerKebabMenu(), + + ViewerKebabMenu(originalTheme: originalTheme), ]; - final lockedViewActions = [ - if (isCasting || (asset.hasRemote)) const CastActionButton(iconOnly: true), - const ViewerKebabMenu(), - ]; + final lockedViewActions = [ViewerKebabMenu(originalTheme: originalTheme)]; return IgnorePointer( ignoring: opacity < 255, diff --git a/mobile/lib/presentation/widgets/asset_viewer/viewer_kebab_menu.widget.dart b/mobile/lib/presentation/widgets/asset_viewer/viewer_kebab_menu.widget.dart index 4651b5eea8..ff638ee583 100644 --- a/mobile/lib/presentation/widgets/asset_viewer/viewer_kebab_menu.widget.dart +++ b/mobile/lib/presentation/widgets/asset_viewer/viewer_kebab_menu.widget.dart @@ -1,14 +1,17 @@ -import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/domain/models/events.model.dart'; -import 'package:immich_mobile/domain/utils/event_stream.dart'; +import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart'; +import 'package:immich_mobile/providers/cast.provider.dart'; import 'package:immich_mobile/providers/infrastructure/asset_viewer/current_asset.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; +import 'package:immich_mobile/providers/user.provider.dart'; +import 'package:immich_mobile/utils/action_button.utils.dart'; class ViewerKebabMenu extends ConsumerWidget { - const ViewerKebabMenu({super.key}); + const ViewerKebabMenu({super.key, this.originalTheme}); + + final ThemeData? originalTheme; @override Widget build(BuildContext context, WidgetRef ref) { @@ -17,25 +20,42 @@ class ViewerKebabMenu extends ConsumerWidget { return const SizedBox.shrink(); } - final menuChildren = [ - BaseActionButton( - label: 'about'.tr(), - iconData: Icons.info_outline, - menuItem: true, - onPressed: () => EventStream.shared.emit(const ViewerOpenBottomSheetEvent()), - ), - ]; + final user = ref.watch(currentUserProvider); + final isOwner = asset is RemoteAsset && asset.ownerId == user?.id; + final isCasting = ref.watch(castProvider.select((c) => c.isCasting)); + final timelineOrigin = ref.read(timelineServiceProvider).origin; + + final kebabContext = ViewerKebabMenuButtonContext( + asset: asset, + isOwner: isOwner, + isCasting: isCasting, + timelineOrigin: timelineOrigin, + originalTheme: originalTheme, + ); + + final menuChildren = ViewerKebabMenuButtonBuilder.build(kebabContext, context, ref); return MenuAnchor( consumeOutsideTap: true, style: MenuStyle( backgroundColor: WidgetStatePropertyAll(context.themeData.scaffoldBackgroundColor), + surfaceTintColor: const WidgetStatePropertyAll(Colors.grey), elevation: const WidgetStatePropertyAll(4), shape: const WidgetStatePropertyAll( RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(12))), ), + padding: const WidgetStatePropertyAll(EdgeInsets.symmetric(vertical: 6)), ), - menuChildren: menuChildren, + menuChildren: [ + ConstrainedBox( + constraints: const BoxConstraints(minWidth: 150), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: menuChildren, + ), + ), + ], builder: (context, controller, child) { return IconButton( icon: const Icon(Icons.more_vert_rounded), diff --git a/mobile/lib/utils/action_button.utils.dart b/mobile/lib/utils/action_button.utils.dart index 42729becc9..917ddbebca 100644 --- a/mobile/lib/utils/action_button.utils.dart +++ b/mobile/lib/utils/action_button.utils.dart @@ -1,9 +1,18 @@ -import 'package:flutter/widgets.dart'; +import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/enums.dart'; import 'package:immich_mobile/domain/models/album/album.model.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; +import 'package:immich_mobile/domain/models/events.model.dart'; +import 'package:immich_mobile/domain/services/timeline.service.dart'; +import 'package:immich_mobile/domain/utils/event_stream.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/advanced_info_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/archive_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/cast_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/delete_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/delete_local_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/delete_permanent_action_button.widget.dart'; @@ -19,6 +28,7 @@ import 'package:immich_mobile/presentation/widgets/action_buttons/trash_action_b import 'package:immich_mobile/presentation/widgets/action_buttons/unarchive_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/unstack_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/upload_action_button.widget.dart'; +import 'package:immich_mobile/routing/router.dart'; class ActionButtonContext { final BaseAsset asset; @@ -164,3 +174,98 @@ class ActionButtonBuilder { return _actionTypes.where((type) => type.shouldShow(context)).map((type) => type.buildButton(context)).toList(); } } + +class ViewerKebabMenuButtonContext { + final BaseAsset asset; + final bool isOwner; + final bool isCasting; + final TimelineOrigin timelineOrigin; + final ThemeData? originalTheme; + + const ViewerKebabMenuButtonContext({ + required this.asset, + required this.isOwner, + required this.isCasting, + required this.timelineOrigin, + this.originalTheme, + }); +} + +enum ViewerKebabMenuButtonType { + openInfo, + viewInTimeline, + cast, + download; + + /// Defines which group each button belongs to. + /// Buttons in the same group will be displayed together, + /// with dividers separating different groups. + int get group => switch (this) { + ViewerKebabMenuButtonType.openInfo => 0, + ViewerKebabMenuButtonType.viewInTimeline => 1, + ViewerKebabMenuButtonType.cast => 1, + ViewerKebabMenuButtonType.download => 1, + }; + + bool shouldShow(ViewerKebabMenuButtonContext context) { + return switch (this) { + ViewerKebabMenuButtonType.openInfo => true, + ViewerKebabMenuButtonType.viewInTimeline => + context.timelineOrigin != TimelineOrigin.main && + context.timelineOrigin != TimelineOrigin.deepLink && + context.timelineOrigin != TimelineOrigin.trash && + context.timelineOrigin != TimelineOrigin.archive && + context.timelineOrigin != TimelineOrigin.localAlbum && + context.isOwner, + ViewerKebabMenuButtonType.cast => context.isCasting || context.asset.hasRemote, + ViewerKebabMenuButtonType.download => context.asset.isRemoteOnly, + }; + } + + ConsumerWidget buildButton(ViewerKebabMenuButtonContext context, BuildContext buildContext) { + return switch (this) { + ViewerKebabMenuButtonType.openInfo => BaseActionButton( + label: 'info'.tr(), + iconData: Icons.info_outline, + iconColor: context.originalTheme?.iconTheme.color, + menuItem: true, + onPressed: () => EventStream.shared.emit(const ViewerOpenBottomSheetEvent()), + ), + + ViewerKebabMenuButtonType.viewInTimeline => BaseActionButton( + label: 'view_in_timeline'.t(context: buildContext), + iconData: Icons.image_search, + iconColor: context.originalTheme?.iconTheme.color, + menuItem: true, + onPressed: () async { + await buildContext.maybePop(); + await buildContext.navigateTo(const TabShellRoute(children: [MainTimelineRoute()])); + EventStream.shared.emit(ScrollToDateEvent(context.asset.createdAt)); + }, + ), + ViewerKebabMenuButtonType.cast => const CastActionButton(menuItem: true), + ViewerKebabMenuButtonType.download => const DownloadActionButton(source: ActionSource.viewer, menuItem: true), + }; + } +} + +class ViewerKebabMenuButtonBuilder { + static List build(ViewerKebabMenuButtonContext context, BuildContext buildContext, WidgetRef ref) { + final visibleButtons = ViewerKebabMenuButtonType.values.where((type) => type.shouldShow(context)).toList(); + + if (visibleButtons.isEmpty) return []; + + final List result = []; + int? lastGroup; + + for (final type in visibleButtons) { + if (lastGroup != null && type.group != lastGroup) { + result.add(const Divider(height: 1)); + } + result.add(type.buildButton(context, buildContext).build(buildContext, ref)); + lastGroup = type.group; + } + + return result; + } +} From 6d499c782a9e69b26b67b9f63368d897d84ed854 Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 9 Dec 2025 17:27:01 -0600 Subject: [PATCH 19/35] chore: update ui lib (#24483) --- pnpm-lock.yaml | 10 +++++----- web/package.json | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 09a047b9e0..13b81356c3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -717,8 +717,8 @@ importers: specifier: file:../open-api/typescript-sdk version: link:../open-api/typescript-sdk '@immich/ui': - specifier: ^0.50.0 - version: 0.50.0(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2) + specifier: ^0.50.1 + version: 0.50.1(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2) '@mapbox/mapbox-gl-rtl-text': specifier: 0.2.3 version: 0.2.3(mapbox-gl@1.13.3) @@ -2989,8 +2989,8 @@ packages: peerDependencies: svelte: ^5.0.0 - '@immich/ui@0.50.0': - resolution: {integrity: sha512-7AW9SRZTAgal8xlkUAxm7o4+pSG7HcKb+Bh9JpWLaDRRdGyPCZMmsNa9CjZglOQ7wkAD07tQ9u4+zezBLe0dlQ==} + '@immich/ui@0.50.1': + resolution: {integrity: sha512-fNlQGh75ZFa/UZAgJaYk9/ItHOXHNNzN4CunjCmE7WocVVkUZbUxopN9Ku3F5GULSqD/zJ5gNO6PQAZ1ZoSaaQ==} peerDependencies: svelte: ^5.0.0 @@ -14701,7 +14701,7 @@ snapshots: dependencies: svelte: 5.45.2 - '@immich/ui@0.50.0(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2)': + '@immich/ui@0.50.1(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2)': dependencies: '@immich/svelte-markdown-preprocess': 0.1.0(svelte@5.45.2) '@internationalized/date': 3.10.0 diff --git a/web/package.json b/web/package.json index 82065d74bf..b3cc6d2c2f 100644 --- a/web/package.json +++ b/web/package.json @@ -28,7 +28,7 @@ "@formatjs/icu-messageformat-parser": "^2.9.8", "@immich/justified-layout-wasm": "^0.4.3", "@immich/sdk": "file:../open-api/typescript-sdk", - "@immich/ui": "^0.50.0", + "@immich/ui": "^0.50.1", "@mapbox/mapbox-gl-rtl-text": "0.2.3", "@mdi/js": "^7.4.47", "@photo-sphere-viewer/core": "^5.14.0", From 15e58595fd2f046f5bd40415b8287881cf64e9ea Mon Sep 17 00:00:00 2001 From: Kurt McKee Date: Wed, 10 Dec 2025 16:17:08 -0600 Subject: [PATCH 20/35] fix(mobile): iOS local permission dialog extra whitespace (#24491) Fix a iOS rendering issue caused by extra whitespace --- mobile/ios/Runner/Info.plist | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mobile/ios/Runner/Info.plist b/mobile/ios/Runner/Info.plist index 7a3a9261ae..b597912d0f 100644 --- a/mobile/ios/Runner/Info.plist +++ b/mobile/ios/Runner/Info.plist @@ -137,8 +137,7 @@ NSFaceIDUsageDescription We need to use FaceID to allow access to your locked folder NSLocalNetworkUsageDescription - We need local network permission to connect to the local server using IP address and - allow the casting feature to work + We need local network permission to connect to the local server using IP address and allow the casting feature to work NSLocationAlwaysAndWhenInUseUsageDescription We require this permission to access the local WiFi name for background upload mechanism NSLocationUsageDescription From b052893a1eefdb3b41e6080b5085f456f6108d4f Mon Sep 17 00:00:00 2001 From: shenlong <139912620+shenlong-tanwen@users.noreply.github.com> Date: Wed, 10 Dec 2025 16:18:01 -0600 Subject: [PATCH 21/35] feat(mobile): immich-ui icon button (#24502) * feat(mobile): immich-ui icon button * fix lint --------- Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com> --- .../pages/dev/feat_in_development.page.dart | 173 ------------------ .../pages/dev/ui_showcase.page.dart | 51 ++++++ .../pages/editing/drift_crop.page.dart | 31 ++-- mobile/lib/routing/router.dart | 4 +- mobile/lib/routing/router.gr.dart | 32 ++-- mobile/lib/widgets/common/immich_app_bar.dart | 4 +- .../widgets/common/immich_sliver_app_bar.dart | 4 +- mobile/packages/ui/lib/immich_ui.dart | 3 + .../ui/lib/src/buttons/close_button.dart | 25 +++ .../ui/lib/src/buttons/icon_button.dart | 48 +++++ mobile/packages/ui/lib/src/types.dart | 9 + mobile/packages/ui/pubspec.lock | 55 ++++++ mobile/packages/ui/pubspec.yaml | 12 ++ mobile/pubspec.lock | 7 + mobile/pubspec.yaml | 2 + 15 files changed, 251 insertions(+), 209 deletions(-) delete mode 100644 mobile/lib/presentation/pages/dev/feat_in_development.page.dart create mode 100644 mobile/lib/presentation/pages/dev/ui_showcase.page.dart create mode 100644 mobile/packages/ui/lib/immich_ui.dart create mode 100644 mobile/packages/ui/lib/src/buttons/close_button.dart create mode 100644 mobile/packages/ui/lib/src/buttons/icon_button.dart create mode 100644 mobile/packages/ui/lib/src/types.dart create mode 100644 mobile/packages/ui/pubspec.lock create mode 100644 mobile/packages/ui/pubspec.yaml diff --git a/mobile/lib/presentation/pages/dev/feat_in_development.page.dart b/mobile/lib/presentation/pages/dev/feat_in_development.page.dart deleted file mode 100644 index 491c38e7a8..0000000000 --- a/mobile/lib/presentation/pages/dev/feat_in_development.page.dart +++ /dev/null @@ -1,173 +0,0 @@ -import 'dart:async'; - -import 'package:auto_route/auto_route.dart'; -import 'package:drift/drift.dart' hide Column; -import 'package:easy_localization/easy_localization.dart'; -import 'package:flutter/material.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/providers/background_sync.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/asset.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/platform.provider.dart'; -import 'package:immich_mobile/providers/user.provider.dart'; -import 'package:immich_mobile/routing/router.dart'; -import 'package:logging/logging.dart'; - -final _features = [ - _Feature( - name: 'Main Timeline', - icon: Icons.timeline_rounded, - onTap: (ctx, _) => ctx.pushRoute(const TabShellRoute()), - ), - _Feature( - name: 'Selection Mode Timeline', - icon: Icons.developer_mode_rounded, - onTap: (ctx, ref) async { - final user = ref.watch(currentUserProvider); - if (user == null) { - return Future.value(); - } - - final assets = await ref.read(remoteAssetRepositoryProvider).getSome(user.id); - - final selectedAssets = await ctx.pushRoute>( - DriftAssetSelectionTimelineRoute(lockedSelectionAssets: assets.toSet()), - ); - - Logger("FeaturesInDevelopment").fine("Selected ${selectedAssets?.length ?? 0} assets"); - - return Future.value(); - }, - ), - _Feature(name: '', icon: Icons.vertical_align_center_sharp, onTap: (_, __) => Future.value()), - _Feature( - name: 'Sync Local', - icon: Icons.photo_album_rounded, - onTap: (_, ref) => ref.read(backgroundSyncProvider).syncLocal(), - ), - _Feature( - name: 'Sync Local Full (1)', - icon: Icons.photo_library_rounded, - onTap: (_, ref) => ref.read(backgroundSyncProvider).syncLocal(full: true), - ), - _Feature( - name: 'Hash Local Assets (2)', - icon: Icons.numbers_outlined, - onTap: (_, ref) => ref.read(backgroundSyncProvider).hashAssets(), - ), - _Feature( - name: 'Sync Remote (3)', - icon: Icons.refresh_rounded, - onTap: (_, ref) => ref.read(backgroundSyncProvider).syncRemote(), - ), - _Feature( - name: 'WAL Checkpoint', - icon: Icons.save_rounded, - onTap: (_, ref) => ref.read(driftProvider).customStatement("pragma wal_checkpoint(truncate)"), - ), - _Feature(name: '', icon: Icons.vertical_align_center_sharp, onTap: (_, __) => Future.value()), - _Feature( - name: 'Clear Delta Checkpoint', - icon: Icons.delete_rounded, - onTap: (_, ref) => ref.read(nativeSyncApiProvider).clearSyncCheckpoint(), - ), - _Feature( - name: 'Clear Local Data', - style: const TextStyle(color: Colors.orange, fontWeight: FontWeight.bold), - icon: Icons.delete_forever_rounded, - onTap: (_, ref) async { - final db = ref.read(driftProvider); - await db.localAssetEntity.deleteAll(); - await db.localAlbumEntity.deleteAll(); - await db.localAlbumAssetEntity.deleteAll(); - }, - ), - _Feature( - name: 'Clear Remote Data', - style: const TextStyle(color: Colors.orange, fontWeight: FontWeight.bold), - icon: Icons.delete_sweep_rounded, - onTap: (_, ref) async { - final db = ref.read(driftProvider); - await db.remoteAssetEntity.deleteAll(); - await db.remoteExifEntity.deleteAll(); - await db.remoteAlbumEntity.deleteAll(); - await db.remoteAlbumUserEntity.deleteAll(); - await db.remoteAlbumAssetEntity.deleteAll(); - await db.memoryEntity.deleteAll(); - await db.memoryAssetEntity.deleteAll(); - await db.stackEntity.deleteAll(); - await db.personEntity.deleteAll(); - await db.assetFaceEntity.deleteAll(); - }, - ), - _Feature( - name: 'Local Media Summary', - style: const TextStyle(color: Colors.indigo, fontWeight: FontWeight.bold), - icon: Icons.table_chart_rounded, - onTap: (ctx, _) => ctx.pushRoute(const LocalMediaSummaryRoute()), - ), - _Feature( - name: 'Remote Media Summary', - style: const TextStyle(color: Colors.indigo, fontWeight: FontWeight.bold), - icon: Icons.summarize_rounded, - onTap: (ctx, _) => ctx.pushRoute(const RemoteMediaSummaryRoute()), - ), - _Feature( - name: 'Reset Sqlite', - icon: Icons.table_view_rounded, - style: const TextStyle(color: Colors.red, fontWeight: FontWeight.bold), - onTap: (_, ref) async { - final drift = ref.read(driftProvider); - // ignore: invalid_use_of_protected_member, invalid_use_of_visible_for_testing_member - final migrator = drift.createMigrator(); - for (final entity in drift.allSchemaEntities) { - await migrator.drop(entity); - await migrator.create(entity); - } - }, - ), -]; - -@RoutePage() -class FeatInDevPage extends StatelessWidget { - const FeatInDevPage({super.key}); - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar(title: Text('features_in_development'.tr()), centerTitle: true), - body: Column( - children: [ - Flexible( - flex: 1, - child: ListView.builder( - itemBuilder: (_, index) { - final feat = _features[index]; - return Consumer( - builder: (ctx, ref, _) => ListTile( - title: Text(feat.name, style: feat.style), - trailing: Icon(feat.icon), - visualDensity: VisualDensity.compact, - onTap: () => unawaited(feat.onTap(ctx, ref)), - ), - ); - }, - itemCount: _features.length, - ), - ), - const Divider(height: 0), - ], - ), - ); - } -} - -class _Feature { - const _Feature({required this.name, required this.icon, required this.onTap, this.style}); - - final String name; - final IconData icon; - final TextStyle? style; - final Future Function(BuildContext, WidgetRef _) onTap; -} diff --git a/mobile/lib/presentation/pages/dev/ui_showcase.page.dart b/mobile/lib/presentation/pages/dev/ui_showcase.page.dart new file mode 100644 index 0000000000..01fe928478 --- /dev/null +++ b/mobile/lib/presentation/pages/dev/ui_showcase.page.dart @@ -0,0 +1,51 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_ui/immich_ui.dart'; + +List _showcaseBuilder(Function(ImmichVariant variant, ImmichColor color) builder) { + final children = []; + + final items = [ + (variant: ImmichVariant.filled, title: "Filled Variant"), + (variant: ImmichVariant.ghost, title: "Ghost Variant"), + ]; + + for (final (:variant, :title) in items) { + children.add(Text(title)); + children.add(Row(spacing: 10, children: [for (var color in ImmichColor.values) builder(variant, color)])); + } + + return children; +} + +@RoutePage() +class ImmichUIShowcasePage extends StatelessWidget { + const ImmichUIShowcasePage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Immich UI Showcase')), + body: Padding( + padding: const EdgeInsets.all(20), + child: SingleChildScrollView( + child: Column( + spacing: 10, + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text("IconButton", style: context.textTheme.titleLarge), + ..._showcaseBuilder( + (variant, color) => + ImmichIconButton(icon: Icons.favorite, color: color, variant: variant, onTap: () {}), + ), + Text("CloseButton", style: context.textTheme.titleLarge), + ..._showcaseBuilder((variant, color) => ImmichCloseButton(color: color, variant: variant, onTap: () {})), + ], + ), + ), + ), + ); + } +} diff --git a/mobile/lib/presentation/pages/editing/drift_crop.page.dart b/mobile/lib/presentation/pages/editing/drift_crop.page.dart index d8219e3b3c..1692140cd2 100644 --- a/mobile/lib/presentation/pages/editing/drift_crop.page.dart +++ b/mobile/lib/presentation/pages/editing/drift_crop.page.dart @@ -9,6 +9,7 @@ import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/utils/hooks/crop_controller_hook.dart'; +import 'package:immich_ui/immich_ui.dart'; /// A widget for cropping an image. /// This widget uses [HookWidget] to manage its lifecycle and state. It allows @@ -30,11 +31,13 @@ class DriftCropImagePage extends HookWidget { appBar: AppBar( backgroundColor: context.scaffoldBackgroundColor, title: Text("crop".tr()), - leading: CloseButton(color: context.primaryColor), + leading: const ImmichCloseButton(), actions: [ - IconButton( - icon: Icon(Icons.done_rounded, color: context.primaryColor, size: 24), - onPressed: () async { + ImmichIconButton( + icon: Icons.done_rounded, + color: ImmichColor.primary, + variant: ImmichVariant.ghost, + onTap: () async { final croppedImage = await cropController.croppedImage(); unawaited(context.pushRoute(DriftEditImageRoute(asset: asset, image: croppedImage, isEdited: true))); }, @@ -72,17 +75,17 @@ class DriftCropImagePage extends HookWidget { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - IconButton( - icon: Icon(Icons.rotate_left, color: context.themeData.iconTheme.color), - onPressed: () { - cropController.rotateLeft(); - }, + ImmichIconButton( + icon: Icons.rotate_left, + variant: ImmichVariant.ghost, + color: ImmichColor.secondary, + onTap: () => cropController.rotateLeft(), ), - IconButton( - icon: Icon(Icons.rotate_right, color: context.themeData.iconTheme.color), - onPressed: () { - cropController.rotateRight(); - }, + ImmichIconButton( + icon: Icons.rotate_right, + variant: ImmichVariant.ghost, + color: ImmichColor.secondary, + onTap: () => cropController.rotateRight(), ), ], ), diff --git a/mobile/lib/routing/router.dart b/mobile/lib/routing/router.dart index 383f599331..9c4a193381 100644 --- a/mobile/lib/routing/router.dart +++ b/mobile/lib/routing/router.dart @@ -78,9 +78,9 @@ import 'package:immich_mobile/pages/search/recently_taken.page.dart'; import 'package:immich_mobile/pages/search/search.page.dart'; import 'package:immich_mobile/pages/settings/sync_status.page.dart'; import 'package:immich_mobile/pages/share_intent/share_intent.page.dart'; -import 'package:immich_mobile/presentation/pages/dev/feat_in_development.page.dart'; import 'package:immich_mobile/presentation/pages/dev/main_timeline.page.dart'; import 'package:immich_mobile/presentation/pages/dev/media_stat.page.dart'; +import 'package:immich_mobile/presentation/pages/dev/ui_showcase.page.dart'; import 'package:immich_mobile/presentation/pages/download_info.page.dart'; import 'package:immich_mobile/presentation/pages/drift_activities.page.dart'; import 'package:immich_mobile/presentation/pages/drift_album.page.dart'; @@ -286,7 +286,6 @@ class AppRouter extends RootStackRouter { AutoRoute(page: ShareIntentRoute.page, guards: [_authGuard, _duplicateGuard]), AutoRoute(page: LockedRoute.page, guards: [_authGuard, _lockedGuard, _duplicateGuard]), AutoRoute(page: PinAuthRoute.page, guards: [_authGuard, _duplicateGuard]), - AutoRoute(page: FeatInDevRoute.page, guards: [_authGuard, _duplicateGuard]), AutoRoute(page: LocalMediaSummaryRoute.page, guards: [_authGuard, _duplicateGuard]), AutoRoute(page: RemoteMediaSummaryRoute.page, guards: [_authGuard, _duplicateGuard]), AutoRoute(page: DriftBackupRoute.page, guards: [_authGuard, _duplicateGuard]), @@ -338,6 +337,7 @@ class AppRouter extends RootStackRouter { AutoRoute(page: DriftBackupAssetDetailRoute.page, guards: [_authGuard, _duplicateGuard]), AutoRoute(page: AssetTroubleshootRoute.page, guards: [_authGuard, _duplicateGuard]), AutoRoute(page: DownloadInfoRoute.page, guards: [_authGuard, _duplicateGuard]), + AutoRoute(page: ImmichUIShowcaseRoute.page, guards: [_authGuard, _duplicateGuard]), // required to handle all deeplinks in deep_link.service.dart // auto_route_library#1722 RedirectRoute(path: '*', redirectTo: '/'), diff --git a/mobile/lib/routing/router.gr.dart b/mobile/lib/routing/router.gr.dart index 146b313c2d..939bf73369 100644 --- a/mobile/lib/routing/router.gr.dart +++ b/mobile/lib/routing/router.gr.dart @@ -1648,22 +1648,6 @@ class FavoritesRoute extends PageRouteInfo { ); } -/// generated route for -/// [FeatInDevPage] -class FeatInDevRoute extends PageRouteInfo { - const FeatInDevRoute({List? children}) - : super(FeatInDevRoute.name, initialChildren: children); - - static const String name = 'FeatInDevRoute'; - - static PageInfo page = PageInfo( - name, - builder: (data) { - return const FeatInDevPage(); - }, - ); -} - /// generated route for /// [FilterImagePage] class FilterImageRoute extends PageRouteInfo { @@ -1831,6 +1815,22 @@ class HeaderSettingsRoute extends PageRouteInfo { ); } +/// generated route for +/// [ImmichUIShowcasePage] +class ImmichUIShowcaseRoute extends PageRouteInfo { + const ImmichUIShowcaseRoute({List? children}) + : super(ImmichUIShowcaseRoute.name, initialChildren: children); + + static const String name = 'ImmichUIShowcaseRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + return const ImmichUIShowcasePage(); + }, + ); +} + /// generated route for /// [LibraryPage] class LibraryRoute extends PageRouteInfo { diff --git a/mobile/lib/widgets/common/immich_app_bar.dart b/mobile/lib/widgets/common/immich_app_bar.dart index 2bac100807..b3dc04236c 100644 --- a/mobile/lib/widgets/common/immich_app_bar.dart +++ b/mobile/lib/widgets/common/immich_app_bar.dart @@ -155,8 +155,8 @@ class ImmichAppBar extends ConsumerWidget implements PreferredSizeWidget { ...actions!.map((action) => Padding(padding: const EdgeInsets.only(right: 16), child: action)), if (kDebugMode || kProfileMode) IconButton( - icon: const Icon(Icons.science_rounded), - onPressed: () => context.pushRoute(const FeatInDevRoute()), + icon: const Icon(Icons.palette_rounded), + onPressed: () => context.pushRoute(const ImmichUIShowcaseRoute()), ), if (isCasting) Padding( diff --git a/mobile/lib/widgets/common/immich_sliver_app_bar.dart b/mobile/lib/widgets/common/immich_sliver_app_bar.dart index f68d5c9fda..dd985ebfe2 100644 --- a/mobile/lib/widgets/common/immich_sliver_app_bar.dart +++ b/mobile/lib/widgets/common/immich_sliver_app_bar.dart @@ -74,8 +74,8 @@ class ImmichSliverAppBar extends ConsumerWidget { ...actions!.map((action) => Padding(padding: const EdgeInsets.only(right: 16), child: action)), if ((kDebugMode || kProfileMode) && !isReadonlyModeEnabled) IconButton( - icon: const Icon(Icons.science_rounded), - onPressed: () => context.pushRoute(const FeatInDevRoute()), + icon: const Icon(Icons.palette_rounded), + onPressed: () => context.pushRoute(const ImmichUIShowcaseRoute()), ), if (showUploadButton && !isReadonlyModeEnabled) const Padding(padding: EdgeInsets.only(right: 20), child: _BackupIndicator()), diff --git a/mobile/packages/ui/lib/immich_ui.dart b/mobile/packages/ui/lib/immich_ui.dart new file mode 100644 index 0000000000..2417149f76 --- /dev/null +++ b/mobile/packages/ui/lib/immich_ui.dart @@ -0,0 +1,3 @@ +export 'src/buttons/close_button.dart'; +export 'src/buttons/icon_button.dart'; +export 'src/types.dart'; diff --git a/mobile/packages/ui/lib/src/buttons/close_button.dart b/mobile/packages/ui/lib/src/buttons/close_button.dart new file mode 100644 index 0000000000..c8c5d62a12 --- /dev/null +++ b/mobile/packages/ui/lib/src/buttons/close_button.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; +import 'package:immich_ui/src/buttons/icon_button.dart'; +import 'package:immich_ui/src/types.dart'; + +class ImmichCloseButton extends StatelessWidget { + final VoidCallback? onTap; + final ImmichVariant variant; + final ImmichColor color; + + const ImmichCloseButton({ + super.key, + this.onTap, + this.color = ImmichColor.primary, + this.variant = ImmichVariant.ghost, + }); + + @override + Widget build(BuildContext context) => ImmichIconButton( + key: key, + icon: Icons.close, + color: color, + variant: variant, + onTap: onTap ?? () => Navigator.of(context).pop(), + ); +} diff --git a/mobile/packages/ui/lib/src/buttons/icon_button.dart b/mobile/packages/ui/lib/src/buttons/icon_button.dart new file mode 100644 index 0000000000..5c62ee8eda --- /dev/null +++ b/mobile/packages/ui/lib/src/buttons/icon_button.dart @@ -0,0 +1,48 @@ +import 'package:flutter/material.dart'; +import 'package:immich_ui/src/types.dart'; + +class ImmichIconButton extends StatelessWidget { + final IconData icon; + final VoidCallback onTap; + final ImmichVariant variant; + final ImmichColor color; + + const ImmichIconButton({ + super.key, + required this.icon, + required this.onTap, + this.color = ImmichColor.primary, + this.variant = ImmichVariant.filled, + }); + + @override + Widget build(BuildContext context) { + final background = switch (variant) { + ImmichVariant.filled => switch (color) { + ImmichColor.primary => Theme.of(context).colorScheme.primary, + ImmichColor.secondary => Theme.of(context).colorScheme.secondary, + }, + ImmichVariant.ghost => Colors.transparent, + }; + + final foreground = switch (variant) { + ImmichVariant.filled => switch (color) { + ImmichColor.primary => Theme.of(context).colorScheme.onPrimary, + ImmichColor.secondary => Theme.of(context).colorScheme.onSecondary, + }, + ImmichVariant.ghost => switch (color) { + ImmichColor.primary => Theme.of(context).colorScheme.primary, + ImmichColor.secondary => Theme.of(context).colorScheme.secondary, + }, + }; + + return IconButton( + icon: Icon(icon), + onPressed: onTap, + style: IconButton.styleFrom( + backgroundColor: background, + foregroundColor: foreground, + ), + ); + } +} diff --git a/mobile/packages/ui/lib/src/types.dart b/mobile/packages/ui/lib/src/types.dart new file mode 100644 index 0000000000..2c0c7b7760 --- /dev/null +++ b/mobile/packages/ui/lib/src/types.dart @@ -0,0 +1,9 @@ +enum ImmichVariant { + filled, + ghost, +} + +enum ImmichColor { + primary, + secondary, +} diff --git a/mobile/packages/ui/pubspec.lock b/mobile/packages/ui/pubspec.lock new file mode 100644 index 0000000000..b9d150f174 --- /dev/null +++ b/mobile/packages/ui/pubspec.lock @@ -0,0 +1,55 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + characters: + dependency: transitive + description: + name: characters + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + url: "https://pub.dev" + source: hosted + version: "0.11.1" + meta: + dependency: transitive + description: + name: meta + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + url: "https://pub.dev" + source: hosted + version: "1.16.0" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + url: "https://pub.dev" + source: hosted + version: "2.2.0" +sdks: + dart: ">=3.8.0-0 <4.0.0" diff --git a/mobile/packages/ui/pubspec.yaml b/mobile/packages/ui/pubspec.yaml new file mode 100644 index 0000000000..47b9a9dd8a --- /dev/null +++ b/mobile/packages/ui/pubspec.yaml @@ -0,0 +1,12 @@ +name: immich_ui +publish_to: none + +environment: + sdk: '>=3.0.0 <4.0.0' + +dependencies: + flutter: + sdk: flutter + +flutter: + uses-material-design: true \ No newline at end of file diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock index 6a067f509f..3179d71bd1 100644 --- a/mobile/pubspec.lock +++ b/mobile/pubspec.lock @@ -1015,6 +1015,13 @@ packages: relative: true source: path version: "0.0.0" + immich_ui: + dependency: "direct main" + description: + path: "packages/ui" + relative: true + source: path + version: "0.0.0" integration_test: dependency: "direct dev" description: flutter diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index a49a012031..1633a54cb6 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -43,6 +43,8 @@ dependencies: hooks_riverpod: ^2.6.1 http: ^1.5.0 image_picker: ^1.2.0 + immich_ui: + path: './packages/ui' intl: ^0.20.2 isar: git: From 5e5bb7e87d57ceaea939182bcf658f628bb4b0f5 Mon Sep 17 00:00:00 2001 From: idubnori Date: Thu, 11 Dec 2025 07:18:55 +0900 Subject: [PATCH 22/35] fix(mobile): versionStatus.message text overflow (#24504) --- .../server_update_notification.dart | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/mobile/lib/widgets/common/app_bar_dialog/server_update_notification.dart b/mobile/lib/widgets/common/app_bar_dialog/server_update_notification.dart index 6068ee022e..179eab8e7d 100644 --- a/mobile/lib/widgets/common/app_bar_dialog/server_update_notification.dart +++ b/mobile/lib/widgets/common/app_bar_dialog/server_update_notification.dart @@ -53,16 +53,18 @@ class ServerUpdateNotification extends HookConsumerWidget { child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - Text( - serverInfoState.versionStatus.message, - textAlign: TextAlign.start, - maxLines: 3, - overflow: TextOverflow.ellipsis, - style: context.textTheme.labelLarge, + Expanded( + child: Text( + serverInfoState.versionStatus.message, + textAlign: TextAlign.start, + maxLines: 3, + overflow: TextOverflow.ellipsis, + style: context.textTheme.labelLarge, + ), ), if (serverInfoState.versionStatus == VersionStatus.serverOutOfDate || serverInfoState.versionStatus == VersionStatus.clientOutOfDate) ...[ - const Spacer(), + const SizedBox(width: 8), TextButton( onPressed: openUpdateLink, style: TextButton.styleFrom( From 4cbce072bef74167ad05fb2f6055b46c5d08f37d Mon Sep 17 00:00:00 2001 From: Noel S Date: Wed, 10 Dec 2025 14:29:36 -0800 Subject: [PATCH 23/35] fix(docs): slow upload speed with example nginx reverse proxy config (#24490) * increase buffer size * increase further * increase buffer further --- docs/docs/administration/reverse-proxy.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/docs/administration/reverse-proxy.md b/docs/docs/administration/reverse-proxy.md index 765c889e23..8dd1674448 100644 --- a/docs/docs/administration/reverse-proxy.md +++ b/docs/docs/administration/reverse-proxy.md @@ -24,6 +24,9 @@ server { # disable buffering uploads to prevent OOM on reverse proxy server and make uploads twice as fast (no pause) proxy_request_buffering off; + # increase body buffer to avoid limiting upload speed + client_body_buffer_size 1024k; + # Set headers proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; From 5821f2fe613c76573bf10c9b6163bc1eeffe7ba2 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 11 Dec 2025 00:59:03 +0100 Subject: [PATCH 24/35] chore(deps): update github-actions (#24477) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build-mobile.yml | 2 +- .github/workflows/cli.yml | 2 +- .github/workflows/codeql-analysis.yml | 6 +++--- .github/workflows/docs-build.yml | 2 +- .github/workflows/fix-format.yml | 4 ++-- .github/workflows/merge-translations.yml | 2 +- .github/workflows/prepare-release.yml | 8 ++++---- .github/workflows/release-pr.yml | 6 +++--- .github/workflows/release.yml | 4 ++-- .github/workflows/sdk.yml | 2 +- .github/workflows/test.yml | 26 ++++++++++++------------ 11 files changed, 32 insertions(+), 32 deletions(-) diff --git a/.github/workflows/build-mobile.yml b/.github/workflows/build-mobile.yml index b49f63fc68..10dc88088f 100644 --- a/.github/workflows/build-mobile.yml +++ b/.github/workflows/build-mobile.yml @@ -108,7 +108,7 @@ jobs: working-directory: ./mobile run: printf "%s" $KEY_JKS | base64 -d > android/key.jks - - uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 + - uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5.1.0 with: distribution: 'zulu' java-version: '17' diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index ef7300f7d4..8bf8da30d7 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -44,7 +44,7 @@ jobs: uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 - name: Setup Node - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 + uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 with: node-version-file: './cli/.nvmrc' registry-url: 'https://registry.npmjs.org' diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index dafc6eee3b..20a5e23c0c 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -57,7 +57,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4.31.5 + uses: github/codeql-action/init@cf1bb45a277cb3c205638b2cd5c984db1c46a412 # v4.31.7 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -70,7 +70,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4.31.5 + uses: github/codeql-action/autobuild@cf1bb45a277cb3c205638b2cd5c984db1c46a412 # v4.31.7 # ℹ️ Command-line programs to run using the OS shell. # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun @@ -83,6 +83,6 @@ jobs: # ./location_of_script_within_repo/buildscript.sh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4.31.5 + uses: github/codeql-action/analyze@cf1bb45a277cb3c205638b2cd5c984db1c46a412 # v4.31.7 with: category: '/language:${{matrix.language}}' diff --git a/.github/workflows/docs-build.yml b/.github/workflows/docs-build.yml index 24cb804e77..680cd0318c 100644 --- a/.github/workflows/docs-build.yml +++ b/.github/workflows/docs-build.yml @@ -69,7 +69,7 @@ jobs: uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 - name: Setup Node - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 + uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 with: node-version-file: './docs/.nvmrc' cache: 'pnpm' diff --git a/.github/workflows/fix-format.yml b/.github/workflows/fix-format.yml index d3db51348b..f77ca48b41 100644 --- a/.github/workflows/fix-format.yml +++ b/.github/workflows/fix-format.yml @@ -16,7 +16,7 @@ jobs: steps: - name: Generate a token id: generate-token - uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2.2.0 + uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1 with: app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} @@ -32,7 +32,7 @@ jobs: uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 - name: Setup Node - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 + uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 with: node-version-file: './server/.nvmrc' cache: 'pnpm' diff --git a/.github/workflows/merge-translations.yml b/.github/workflows/merge-translations.yml index 92d2783441..392dec5e37 100644 --- a/.github/workflows/merge-translations.yml +++ b/.github/workflows/merge-translations.yml @@ -31,7 +31,7 @@ jobs: - name: Generate a token id: generate_token if: ${{ inputs.skip != true }} - uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2.2.0 + uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1 with: app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml index e2543a53d0..6aada80220 100644 --- a/.github/workflows/prepare-release.yml +++ b/.github/workflows/prepare-release.yml @@ -49,7 +49,7 @@ jobs: steps: - name: Generate a token id: generate-token - uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2.2.0 + uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1 with: app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} @@ -68,7 +68,7 @@ jobs: uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 - name: Setup Node - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 + uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 with: node-version-file: './server/.nvmrc' cache: 'pnpm' @@ -126,7 +126,7 @@ jobs: steps: - name: Generate a token id: generate-token - uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2.2.0 + uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1 with: app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} @@ -144,7 +144,7 @@ jobs: github-token: ${{ steps.generate-token.outputs.token }} - name: Create draft release - uses: softprops/action-gh-release@5be0e66d93ac7ed76da52eca8bb058f665c3a5fe # v2.4.2 + uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0 with: draft: true tag_name: ${{ env.IMMICH_VERSION }} diff --git a/.github/workflows/release-pr.yml b/.github/workflows/release-pr.yml index 204c0a853c..4a06957203 100644 --- a/.github/workflows/release-pr.yml +++ b/.github/workflows/release-pr.yml @@ -17,7 +17,7 @@ jobs: steps: - name: Generate a token id: generate-token - uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2.2.0 + uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1 with: app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} @@ -36,7 +36,7 @@ jobs: uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 - name: Setup Node - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 + uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 with: node-version-file: './server/.nvmrc' cache: 'pnpm' @@ -159,7 +159,7 @@ jobs: - name: Create PR id: create-pr - uses: peter-evans/create-pull-request@84ae59a2cdc2258d6fa0732dd66352dddae2a412 # v7.0.9 + uses: peter-evans/create-pull-request@22a9089034f40e5a961c8808d113e2c98fb63676 # v7.0.11 with: token: ${{ steps.generate-token.outputs.token }} commit-message: 'chore: release ${{ steps.bump-type.outputs.next }}' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1f6f7cf5cf..cb64cd37cf 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -52,7 +52,7 @@ jobs: steps: - name: Generate a token id: generate-token - uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2.2.0 + uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1 with: app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} @@ -80,7 +80,7 @@ jobs: github-token: ${{ steps.generate-token.outputs.token }} - name: Create draft release - uses: softprops/action-gh-release@5be0e66d93ac7ed76da52eca8bb058f665c3a5fe # v2.4.2 + uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0 with: tag_name: ${{ steps.version.outputs.result }} token: ${{ steps.generate-token.outputs.token }} diff --git a/.github/workflows/sdk.yml b/.github/workflows/sdk.yml index dc8cc34cb2..9c70922df1 100644 --- a/.github/workflows/sdk.yml +++ b/.github/workflows/sdk.yml @@ -31,7 +31,7 @@ jobs: uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 # Setup .npmrc file to publish to npm - - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 + - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 with: node-version-file: './open-api/typescript-sdk/.nvmrc' registry-url: 'https://registry.npmjs.org' diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f02114a39a..c5d196a084 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -77,7 +77,7 @@ jobs: - name: Setup pnpm uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 - name: Setup Node - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 + uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 with: node-version-file: './server/.nvmrc' cache: 'pnpm' @@ -121,7 +121,7 @@ jobs: - name: Setup pnpm uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 - name: Setup Node - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 + uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 with: node-version-file: './cli/.nvmrc' cache: 'pnpm' @@ -168,7 +168,7 @@ jobs: - name: Setup pnpm uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 - name: Setup Node - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 + uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 with: node-version-file: './cli/.nvmrc' cache: 'pnpm' @@ -210,7 +210,7 @@ jobs: - name: Setup pnpm uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 - name: Setup Node - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 + uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 with: node-version-file: './web/.nvmrc' cache: 'pnpm' @@ -254,7 +254,7 @@ jobs: - name: Setup pnpm uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 - name: Setup Node - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 + uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 with: node-version-file: './web/.nvmrc' cache: 'pnpm' @@ -292,7 +292,7 @@ jobs: - name: Setup pnpm uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 - name: Setup Node - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 + uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 with: node-version-file: './web/.nvmrc' cache: 'pnpm' @@ -340,7 +340,7 @@ jobs: - name: Setup pnpm uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 - name: Setup Node - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 + uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 with: node-version-file: './e2e/.nvmrc' cache: 'pnpm' @@ -387,7 +387,7 @@ jobs: - name: Setup pnpm uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 - name: Setup Node - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 + uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 with: node-version-file: './server/.nvmrc' cache: 'pnpm' @@ -426,7 +426,7 @@ jobs: - name: Setup pnpm uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 - name: Setup Node - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 + uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 with: node-version-file: './e2e/.nvmrc' cache: 'pnpm' @@ -481,7 +481,7 @@ jobs: - name: Setup pnpm uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 - name: Setup Node - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 + uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 with: node-version-file: './e2e/.nvmrc' cache: 'pnpm' @@ -617,7 +617,7 @@ jobs: - name: Setup pnpm uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 - name: Setup Node - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 + uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 with: node-version-file: './.github/.nvmrc' cache: 'pnpm' @@ -668,7 +668,7 @@ jobs: - name: Setup pnpm uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 - name: Setup Node - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 + uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 with: node-version-file: './server/.nvmrc' cache: 'pnpm' @@ -730,7 +730,7 @@ jobs: - name: Setup pnpm uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 - name: Setup Node - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 + uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 with: node-version-file: './server/.nvmrc' cache: 'pnpm' From f0f1d279c4bf956a6604c1fc1b16c9a58af598cf Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 11 Dec 2025 00:59:20 +0100 Subject: [PATCH 25/35] chore(deps): update prom/prometheus docker digest to d936808 (#24475) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- docker/docker-compose.prod.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/docker-compose.prod.yml b/docker/docker-compose.prod.yml index 1aae886bc6..129f46b046 100644 --- a/docker/docker-compose.prod.yml +++ b/docker/docker-compose.prod.yml @@ -83,7 +83,7 @@ services: container_name: immich_prometheus ports: - 9090:9090 - image: prom/prometheus@sha256:49214755b6153f90a597adcbff0252cc61069f8ab69ce8411285cd4a560e8038 + image: prom/prometheus@sha256:d936808bdea528155c0154a922cd42fd75716b8bb7ba302641350f9f3eaeba09 volumes: - ./prometheus.yml:/etc/prometheus/prometheus.yml - prometheus-data:/prometheus From cbdf5011f983b75f7a539a41f8ff557766b512b1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 11 Dec 2025 00:59:39 +0100 Subject: [PATCH 26/35] chore(deps): update docker.io/valkey/valkey:9 docker digest to fb8d272 (#24474) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- docker/docker-compose.dev.yml | 2 +- docker/docker-compose.prod.yml | 2 +- docker/docker-compose.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docker/docker-compose.dev.yml b/docker/docker-compose.dev.yml index 4b1a69d133..4c74d1d640 100644 --- a/docker/docker-compose.dev.yml +++ b/docker/docker-compose.dev.yml @@ -127,7 +127,7 @@ services: redis: container_name: immich_redis - image: docker.io/valkey/valkey:9@sha256:4503e204c900a00ad393bec83c8c7c4c76b0529cd629e23b34b52011aefd1d27 + image: docker.io/valkey/valkey:9@sha256:fb8d272e529ea567b9bf1302245796f21a2672b8368ca3fcb938ac334e613c8f healthcheck: test: redis-cli ping || exit 1 diff --git a/docker/docker-compose.prod.yml b/docker/docker-compose.prod.yml index 129f46b046..21178d8d76 100644 --- a/docker/docker-compose.prod.yml +++ b/docker/docker-compose.prod.yml @@ -56,7 +56,7 @@ services: redis: container_name: immich_redis - image: docker.io/valkey/valkey:9@sha256:4503e204c900a00ad393bec83c8c7c4c76b0529cd629e23b34b52011aefd1d27 + image: docker.io/valkey/valkey:9@sha256:fb8d272e529ea567b9bf1302245796f21a2672b8368ca3fcb938ac334e613c8f healthcheck: test: redis-cli ping || exit 1 restart: always diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 51b6d3e860..f5dfb1233f 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -49,7 +49,7 @@ services: redis: container_name: immich_redis - image: docker.io/valkey/valkey:9@sha256:4503e204c900a00ad393bec83c8c7c4c76b0529cd629e23b34b52011aefd1d27 + image: docker.io/valkey/valkey:9@sha256:fb8d272e529ea567b9bf1302245796f21a2672b8368ca3fcb938ac334e613c8f healthcheck: test: redis-cli ping || exit 1 restart: always From 161147af5161a95e8749b62dcd46df26348b5d90 Mon Sep 17 00:00:00 2001 From: Min Idzelis Date: Wed, 10 Dec 2025 19:07:31 -0500 Subject: [PATCH 27/35] feat: timeline-manager improvement to use AssetResponseDto efficiently (#24421) --- .../lib/components/timeline/Timeline.svelte | 2 +- .../internal/search-support.svelte.ts | 12 +++++++---- .../timeline-manager.svelte.spec.ts | 1 + .../timeline-manager.svelte.ts | 20 +++++++++++-------- web/src/lib/utils/timeline-util.ts | 11 +++++++--- 5 files changed, 30 insertions(+), 16 deletions(-) diff --git a/web/src/lib/components/timeline/Timeline.svelte b/web/src/lib/components/timeline/Timeline.svelte index ba9cf37bff..6e21479acc 100644 --- a/web/src/lib/components/timeline/Timeline.svelte +++ b/web/src/lib/components/timeline/Timeline.svelte @@ -188,7 +188,7 @@ // the performance benefits of deferred layouts while still supporting deep linking // to assets at the end of the timeline. timelineManager.isScrollingOnLoad = true; - const monthGroup = await timelineManager.findMonthGroupForAsset(assetId); + const monthGroup = await timelineManager.findMonthGroupForAsset({ id: assetId }); if (!monthGroup) { return false; } diff --git a/web/src/lib/managers/timeline-manager/internal/search-support.svelte.ts b/web/src/lib/managers/timeline-manager/internal/search-support.svelte.ts index f889456c20..7082673700 100644 --- a/web/src/lib/managers/timeline-manager/internal/search-support.svelte.ts +++ b/web/src/lib/managers/timeline-manager/internal/search-support.svelte.ts @@ -1,5 +1,5 @@ import { plainDateTimeCompare, type TimelineYearMonth } from '$lib/utils/timeline-util'; -import { AssetOrder } from '@immich/sdk'; +import { AssetOrder, type AssetResponseDto } from '@immich/sdk'; import { DateTime } from 'luxon'; import type { MonthGroup } from '../month-group.svelte'; import { TimelineManager } from '../timeline-manager.svelte'; @@ -7,12 +7,16 @@ import type { AssetDescriptor, Direction, TimelineAsset } from '../types'; export async function getAssetWithOffset( timelineManager: TimelineManager, - assetDescriptor: AssetDescriptor, + assetDescriptor: AssetDescriptor | AssetResponseDto, interval: 'asset' | 'day' | 'month' | 'year' = 'asset', direction: Direction, ): Promise { - const { asset, monthGroup } = findMonthGroupForAsset(timelineManager, assetDescriptor.id) ?? {}; - if (!monthGroup || !asset) { + const monthGroup = await timelineManager.findMonthGroupForAsset(assetDescriptor); + if (!monthGroup) { + return; + } + const asset = monthGroup.findAssetById(assetDescriptor); + if (!asset) { return; } diff --git a/web/src/lib/managers/timeline-manager/timeline-manager.svelte.spec.ts b/web/src/lib/managers/timeline-manager/timeline-manager.svelte.spec.ts index bb58704214..8e31f28138 100644 --- a/web/src/lib/managers/timeline-manager/timeline-manager.svelte.spec.ts +++ b/web/src/lib/managers/timeline-manager/timeline-manager.svelte.spec.ts @@ -524,6 +524,7 @@ describe('TimelineManager', () => { { count: 3, timeBucket: '2024-01-01T00:00:00.000Z' }, ]); sdkMock.getTimeBucket.mockImplementation(({ timeBucket }) => Promise.resolve(bucketAssetsResponse[timeBucket])); + sdkMock.getAssetInfo.mockRejectedValue(new Error('Asset not found')); await timelineManager.updateViewport({ width: 1588, height: 1000 }); }); diff --git a/web/src/lib/managers/timeline-manager/timeline-manager.svelte.ts b/web/src/lib/managers/timeline-manager/timeline-manager.svelte.ts index feba73a0f8..b6c43480ef 100644 --- a/web/src/lib/managers/timeline-manager/timeline-manager.svelte.ts +++ b/web/src/lib/managers/timeline-manager/timeline-manager.svelte.ts @@ -16,12 +16,13 @@ import { WebsocketSupport } from '$lib/managers/timeline-manager/internal/websoc import { CancellableTask } from '$lib/utils/cancellable-task'; import { PersistedLocalStorage } from '$lib/utils/persisted'; import { + isAssetResponseDto, setDifference, toTimelineAsset, type TimelineDateTime, type TimelineYearMonth, } from '$lib/utils/timeline-util'; -import { AssetOrder, getAssetInfo, getTimeBuckets } from '@immich/sdk'; +import { AssetOrder, getAssetInfo, getTimeBuckets, type AssetResponseDto } from '@immich/sdk'; import { clamp, isEqual } from 'lodash-es'; import { SvelteDate, SvelteSet } from 'svelte/reactivity'; import { DayGroup } from './day-group.svelte'; @@ -343,27 +344,30 @@ export class TimelineManager extends VirtualScrollManager { this.addAssetsUpsertSegments([...notExcluded]); } - async findMonthGroupForAsset(id: string) { + async findMonthGroupForAsset(asset: AssetDescriptor | AssetResponseDto) { if (!this.isInitialized) { await this.initTask.waitUntilCompletion(); } + const { id } = asset; let { monthGroup } = findMonthGroupForAssetUtil(this, id) ?? {}; if (monthGroup) { return monthGroup; } - const response = await getAssetInfo({ ...authManager.params, id }).catch(() => null); + const response = isAssetResponseDto(asset) + ? asset + : await getAssetInfo({ ...authManager.params, id }).catch(() => null); if (!response) { return; } - const asset = toTimelineAsset(response); - if (!asset || this.isExcluded(asset)) { + const timelineAsset = toTimelineAsset(response); + if (this.isExcluded(timelineAsset)) { return; } - monthGroup = await this.#loadMonthGroupAtTime(asset.localDateTime, { cancelable: false }); + monthGroup = await this.#loadMonthGroupAtTime(timelineAsset.localDateTime, { cancelable: false }); if (monthGroup?.findAssetById({ id })) { return monthGroup; } @@ -532,14 +536,14 @@ export class TimelineManager extends VirtualScrollManager { } async getLaterAsset( - assetDescriptor: AssetDescriptor, + assetDescriptor: AssetDescriptor | AssetResponseDto, interval: 'asset' | 'day' | 'month' | 'year' = 'asset', ): Promise { return await getAssetWithOffset(this, assetDescriptor, interval, 'later'); } async getEarlierAsset( - assetDescriptor: AssetDescriptor, + assetDescriptor: AssetDescriptor | AssetResponseDto, interval: 'asset' | 'day' | 'month' | 'year' = 'asset', ): Promise { return await getAssetWithOffset(this, assetDescriptor, interval, 'earlier'); diff --git a/web/src/lib/utils/timeline-util.ts b/web/src/lib/utils/timeline-util.ts index 3326676f3c..f8fb49b61f 100644 --- a/web/src/lib/utils/timeline-util.ts +++ b/web/src/lib/utils/timeline-util.ts @@ -1,4 +1,4 @@ -import type { TimelineAsset, ViewportTopMonth } from '$lib/managers/timeline-manager/types'; +import type { AssetDescriptor, TimelineAsset, ViewportTopMonth } from '$lib/managers/timeline-manager/types'; import { locale } from '$lib/stores/preferences.store'; import { getAssetRatio } from '$lib/utils/asset-utils'; import { AssetTypeEnum, type AssetResponseDto } from '@immich/sdk'; @@ -192,8 +192,13 @@ export const toTimelineAsset = (unknownAsset: AssetResponseDto | TimelineAsset): }; }; -export const isTimelineAsset = (unknownAsset: AssetResponseDto | TimelineAsset): unknownAsset is TimelineAsset => - (unknownAsset as TimelineAsset).ratio !== undefined; +export const isTimelineAsset = ( + unknownAsset: AssetDescriptor | AssetResponseDto | TimelineAsset, +): unknownAsset is TimelineAsset => (unknownAsset as TimelineAsset).ratio !== undefined; + +export const isAssetResponseDto = ( + unknownAsset: AssetDescriptor | AssetResponseDto | TimelineAsset, +): unknownAsset is AssetResponseDto => (unknownAsset as AssetResponseDto).type !== undefined; export const isTimelineAssets = (assets: AssetResponseDto[] | TimelineAsset[]): assets is TimelineAsset[] => assets.length === 0 || 'ratio' in assets[0]; From baad38f0e6a45d1c3c90541c0667fdc088964a6e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 11 Dec 2025 00:13:06 +0000 Subject: [PATCH 28/35] fix(deps): update typescript-projects (#24476) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Daniel Dietzler --- plugins/package-lock.json | 214 +++---- pnpm-lock.yaml | 1234 +++++++++++++++++++------------------ web/package.json | 4 +- 3 files changed, 730 insertions(+), 722 deletions(-) diff --git a/plugins/package-lock.json b/plugins/package-lock.json index 2cdf7b7e53..ca3c99b516 100644 --- a/plugins/package-lock.json +++ b/plugins/package-lock.json @@ -15,9 +15,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.0.tgz", - "integrity": "sha512-KuZrd2hRjz01y5JK9mEBSD3Vj3mbCvemhT466rSuJYeE/hjuBrHfjjcjMdTm/sz7au+++sdbJZJmuBwQLuw68A==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.1.tgz", + "integrity": "sha512-HHB50pdsBX6k47S4u5g/CaLjqS3qwaOVE5ILsq64jyzgMhLuCuZ8rGzM9yhsAjfjkbgUPMzZEPa7DAp7yz6vuA==", "cpu": [ "ppc64" ], @@ -32,9 +32,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.0.tgz", - "integrity": "sha512-j67aezrPNYWJEOHUNLPj9maeJte7uSMM6gMoxfPC9hOg8N02JuQi/T7ewumf4tNvJadFkvLZMlAq73b9uwdMyQ==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.1.tgz", + "integrity": "sha512-kFqa6/UcaTbGm/NncN9kzVOODjhZW8e+FRdSeypWe6j33gzclHtwlANs26JrupOntlcWmB0u8+8HZo8s7thHvg==", "cpu": [ "arm" ], @@ -49,9 +49,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.0.tgz", - "integrity": "sha512-CC3vt4+1xZrs97/PKDkl0yN7w8edvU2vZvAFGD16n9F0Cvniy5qvzRXjfO1l94efczkkQE6g1x0i73Qf5uthOQ==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.1.tgz", + "integrity": "sha512-45fuKmAJpxnQWixOGCrS+ro4Uvb4Re9+UTieUY2f8AEc+t7d4AaZ6eUJ3Hva7dtrxAAWHtlEFsXFMAgNnGU9uQ==", "cpu": [ "arm64" ], @@ -66,9 +66,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.0.tgz", - "integrity": "sha512-wurMkF1nmQajBO1+0CJmcN17U4BP6GqNSROP8t0X/Jiw2ltYGLHpEksp9MpoBqkrFR3kv2/te6Sha26k3+yZ9Q==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.1.tgz", + "integrity": "sha512-LBEpOz0BsgMEeHgenf5aqmn/lLNTFXVfoWMUox8CtWWYK9X4jmQzWjoGoNb8lmAYml/tQ/Ysvm8q7szu7BoxRQ==", "cpu": [ "x64" ], @@ -83,9 +83,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.0.tgz", - "integrity": "sha512-uJOQKYCcHhg07DL7i8MzjvS2LaP7W7Pn/7uA0B5S1EnqAirJtbyw4yC5jQ5qcFjHK9l6o/MX9QisBg12kNkdHg==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.1.tgz", + "integrity": "sha512-veg7fL8eMSCVKL7IW4pxb54QERtedFDfY/ASrumK/SbFsXnRazxY4YykN/THYqFnFwJ0aVjiUrVG2PwcdAEqQQ==", "cpu": [ "arm64" ], @@ -100,9 +100,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.0.tgz", - "integrity": "sha512-8mG6arH3yB/4ZXiEnXof5MK72dE6zM9cDvUcPtxhUZsDjESl9JipZYW60C3JGreKCEP+p8P/72r69m4AZGJd5g==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.1.tgz", + "integrity": "sha512-+3ELd+nTzhfWb07Vol7EZ+5PTbJ/u74nC6iv4/lwIU99Ip5uuY6QoIf0Hn4m2HoV0qcnRivN3KSqc+FyCHjoVQ==", "cpu": [ "x64" ], @@ -117,9 +117,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.0.tgz", - "integrity": "sha512-9FHtyO988CwNMMOE3YIeci+UV+x5Zy8fI2qHNpsEtSF83YPBmE8UWmfYAQg6Ux7Gsmd4FejZqnEUZCMGaNQHQw==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.1.tgz", + "integrity": "sha512-/8Rfgns4XD9XOSXlzUDepG8PX+AVWHliYlUkFI3K3GB6tqbdjYqdhcb4BKRd7C0BhZSoaCxhv8kTcBrcZWP+xg==", "cpu": [ "arm64" ], @@ -134,9 +134,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.0.tgz", - "integrity": "sha512-zCMeMXI4HS/tXvJz8vWGexpZj2YVtRAihHLk1imZj4efx1BQzN76YFeKqlDr3bUWI26wHwLWPd3rwh6pe4EV7g==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.1.tgz", + "integrity": "sha512-GITpD8dK9C+r+5yRT/UKVT36h/DQLOHdwGVwwoHidlnA168oD3uxA878XloXebK4Ul3gDBBIvEdL7go9gCUFzQ==", "cpu": [ "x64" ], @@ -151,9 +151,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.0.tgz", - "integrity": "sha512-t76XLQDpxgmq2cNXKTVEB7O7YMb42atj2Re2Haf45HkaUpjM2J0UuJZDuaGbPbamzZ7bawyGFUkodL+zcE+jvQ==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.1.tgz", + "integrity": "sha512-ieMID0JRZY/ZeCrsFQ3Y3NlHNCqIhTprJfDgSB3/lv5jJZ8FX3hqPyXWhe+gvS5ARMBJ242PM+VNz/ctNj//eA==", "cpu": [ "arm" ], @@ -168,9 +168,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.0.tgz", - "integrity": "sha512-AS18v0V+vZiLJyi/4LphvBE+OIX682Pu7ZYNsdUHyUKSoRwdnOsMf6FDekwoAFKej14WAkOef3zAORJgAtXnlQ==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.1.tgz", + "integrity": "sha512-W9//kCrh/6in9rWIBdKaMtuTTzNj6jSeG/haWBADqLLa9P8O5YSRDzgD5y9QBok4AYlzS6ARHifAb75V6G670Q==", "cpu": [ "arm64" ], @@ -185,9 +185,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.0.tgz", - "integrity": "sha512-Mz1jxqm/kfgKkc/KLHC5qIujMvnnarD9ra1cEcrs7qshTUSksPihGrWHVG5+osAIQ68577Zpww7SGapmzSt4Nw==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.1.tgz", + "integrity": "sha512-VIUV4z8GD8rtSVMfAj1aXFahsi/+tcoXXNYmXgzISL+KB381vbSTNdeZHHHIYqFyXcoEhu9n5cT+05tRv13rlw==", "cpu": [ "ia32" ], @@ -202,9 +202,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.0.tgz", - "integrity": "sha512-QbEREjdJeIreIAbdG2hLU1yXm1uu+LTdzoq1KCo4G4pFOLlvIspBm36QrQOar9LFduavoWX2msNFAAAY9j4BDg==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.1.tgz", + "integrity": "sha512-l4rfiiJRN7sTNI//ff65zJ9z8U+k6zcCg0LALU5iEWzY+a1mVZ8iWC1k5EsNKThZ7XCQ6YWtsZ8EWYm7r1UEsg==", "cpu": [ "loong64" ], @@ -219,9 +219,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.0.tgz", - "integrity": "sha512-sJz3zRNe4tO2wxvDpH/HYJilb6+2YJxo/ZNbVdtFiKDufzWq4JmKAiHy9iGoLjAV7r/W32VgaHGkk35cUXlNOg==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.1.tgz", + "integrity": "sha512-U0bEuAOLvO/DWFdygTHWY8C067FXz+UbzKgxYhXC0fDieFa0kDIra1FAhsAARRJbvEyso8aAqvPdNxzWuStBnA==", "cpu": [ "mips64el" ], @@ -236,9 +236,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.0.tgz", - "integrity": "sha512-z9N10FBD0DCS2dmSABDBb5TLAyF1/ydVb+N4pi88T45efQ/w4ohr/F/QYCkxDPnkhkp6AIpIcQKQ8F0ANoA2JA==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.1.tgz", + "integrity": "sha512-NzdQ/Xwu6vPSf/GkdmRNsOfIeSGnh7muundsWItmBsVpMoNPVpM61qNzAVY3pZ1glzzAxLR40UyYM23eaDDbYQ==", "cpu": [ "ppc64" ], @@ -253,9 +253,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.0.tgz", - "integrity": "sha512-pQdyAIZ0BWIC5GyvVFn5awDiO14TkT/19FTmFcPdDec94KJ1uZcmFs21Fo8auMXzD4Tt+diXu1LW1gHus9fhFQ==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.1.tgz", + "integrity": "sha512-7zlw8p3IApcsN7mFw0O1Z1PyEk6PlKMu18roImfl3iQHTnr/yAfYv6s4hXPidbDoI2Q0pW+5xeoM4eTCC0UdrQ==", "cpu": [ "riscv64" ], @@ -270,9 +270,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.0.tgz", - "integrity": "sha512-hPlRWR4eIDDEci953RI1BLZitgi5uqcsjKMxwYfmi4LcwyWo2IcRP+lThVnKjNtk90pLS8nKdroXYOqW+QQH+w==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.1.tgz", + "integrity": "sha512-cGj5wli+G+nkVQdZo3+7FDKC25Uh4ZVwOAK6A06Hsvgr8WqBBuOy/1s+PUEd/6Je+vjfm6stX0kmib5b/O2Ykw==", "cpu": [ "s390x" ], @@ -287,9 +287,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.0.tgz", - "integrity": "sha512-1hBWx4OUJE2cab++aVZ7pObD6s+DK4mPGpemtnAORBvb5l/g5xFGk0vc0PjSkrDs0XaXj9yyob3d14XqvnQ4gw==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.1.tgz", + "integrity": "sha512-z3H/HYI9MM0HTv3hQZ81f+AKb+yEoCRlUby1F80vbQ5XdzEMyY/9iNlAmhqiBKw4MJXwfgsh7ERGEOhrM1niMA==", "cpu": [ "x64" ], @@ -304,9 +304,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.0.tgz", - "integrity": "sha512-6m0sfQfxfQfy1qRuecMkJlf1cIzTOgyaeXaiVaaki8/v+WB+U4hc6ik15ZW6TAllRlg/WuQXxWj1jx6C+dfy3w==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.1.tgz", + "integrity": "sha512-wzC24DxAvk8Em01YmVXyjl96Mr+ecTPyOuADAvjGg+fyBpGmxmcr2E5ttf7Im8D0sXZihpxzO1isus8MdjMCXQ==", "cpu": [ "arm64" ], @@ -321,9 +321,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.0.tgz", - "integrity": "sha512-xbbOdfn06FtcJ9d0ShxxvSn2iUsGd/lgPIO2V3VZIPDbEaIj1/3nBBe1AwuEZKXVXkMmpr6LUAgMkLD/4D2PPA==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.1.tgz", + "integrity": "sha512-1YQ8ybGi2yIXswu6eNzJsrYIGFpnlzEWRl6iR5gMgmsrR0FcNoV1m9k9sc3PuP5rUBLshOZylc9nqSgymI+TYg==", "cpu": [ "x64" ], @@ -338,9 +338,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.0.tgz", - "integrity": "sha512-fWgqR8uNbCQ/GGv0yhzttj6sU/9Z5/Sv/VGU3F5OuXK6J6SlriONKrQ7tNlwBrJZXRYk5jUhuWvF7GYzGguBZQ==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.1.tgz", + "integrity": "sha512-5Z+DzLCrq5wmU7RDaMDe2DVXMRm2tTDvX2KU14JJVBN2CT/qov7XVix85QoJqHltpvAOZUAc3ndU56HSMWrv8g==", "cpu": [ "arm64" ], @@ -355,9 +355,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.0.tgz", - "integrity": "sha512-aCwlRdSNMNxkGGqQajMUza6uXzR/U0dIl1QmLjPtRbLOx3Gy3otfFu/VjATy4yQzo9yFDGTxYDo1FfAD9oRD2A==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.1.tgz", + "integrity": "sha512-Q73ENzIdPF5jap4wqLtsfh8YbYSZ8Q0wnxplOlZUOyZy7B4ZKW8DXGWgTCZmF8VWD7Tciwv5F4NsRf6vYlZtqg==", "cpu": [ "x64" ], @@ -372,9 +372,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.0.tgz", - "integrity": "sha512-nyvsBccxNAsNYz2jVFYwEGuRRomqZ149A39SHWk4hV0jWxKM0hjBPm3AmdxcbHiFLbBSwG6SbpIcUbXjgyECfA==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.1.tgz", + "integrity": "sha512-ajbHrGM/XiK+sXM0JzEbJAen+0E+JMQZ2l4RR4VFwvV9JEERx+oxtgkpoKv1SevhjavK2z2ReHk32pjzktWbGg==", "cpu": [ "arm64" ], @@ -389,9 +389,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.0.tgz", - "integrity": "sha512-Q1KY1iJafM+UX6CFEL+F4HRTgygmEW568YMqDA5UV97AuZSm21b7SXIrRJDwXWPzr8MGr75fUZPV67FdtMHlHA==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.1.tgz", + "integrity": "sha512-IPUW+y4VIjuDVn+OMzHc5FV4GubIwPnsz6ubkvN8cuhEqH81NovB53IUlrlBkPMEPxvNnf79MGBoz8rZ2iW8HA==", "cpu": [ "x64" ], @@ -406,9 +406,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.0.tgz", - "integrity": "sha512-W1eyGNi6d+8kOmZIwi/EDjrL9nxQIQ0MiGqe/AWc6+IaHloxHSGoeRgDRKHFISThLmsewZ5nHFvGFWdBYlgKPg==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.1.tgz", + "integrity": "sha512-RIVRWiljWA6CdVu8zkWcRmGP7iRRIIwvhDKem8UMBjPql2TXM5PkDVvvrzMtj1V+WFPB4K7zkIGM7VzRtFkjdg==", "cpu": [ "arm64" ], @@ -423,9 +423,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.0.tgz", - "integrity": "sha512-30z1aKL9h22kQhilnYkORFYt+3wp7yZsHWus+wSKAJR8JtdfI76LJ4SBdMsCopTR3z/ORqVu5L1vtnHZWVj4cQ==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.1.tgz", + "integrity": "sha512-2BR5M8CPbptC1AK5JbJT1fWrHLvejwZidKx3UMSF0ecHMa+smhi16drIrCEggkgviBwLYd5nwrFLSl5Kho96RQ==", "cpu": [ "ia32" ], @@ -440,9 +440,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.0.tgz", - "integrity": "sha512-aIitBcjQeyOhMTImhLZmtxfdOcuNRpwlPNmlFKPcHQYPhEssw75Cl1TSXJXpMkzaua9FUetx/4OQKq7eJul5Cg==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.1.tgz", + "integrity": "sha512-d5X6RMYv6taIymSk8JBP+nxv8DQAMY6A51GPgusqLdK9wBz5wWIXy1KjTck6HnjE9hqJzJRdk+1p/t5soSbCtw==", "cpu": [ "x64" ], @@ -467,9 +467,9 @@ } }, "node_modules/esbuild": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.0.tgz", - "integrity": "sha512-jd0f4NHbD6cALCyGElNpGAOtWxSq46l9X/sWB0Nzd5er4Kz2YTm+Vl0qKFT9KUJvD8+fiO8AvoHhFvEatfVixA==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.1.tgz", + "integrity": "sha512-yY35KZckJJuVVPXpvjgxiCuVEJT67F6zDeVTv4rizyPrfGBUpZQsvmxnN+C371c2esD/hNMjj4tpBhuueLN7aA==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -480,32 +480,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.0", - "@esbuild/android-arm": "0.27.0", - "@esbuild/android-arm64": "0.27.0", - "@esbuild/android-x64": "0.27.0", - "@esbuild/darwin-arm64": "0.27.0", - "@esbuild/darwin-x64": "0.27.0", - "@esbuild/freebsd-arm64": "0.27.0", - "@esbuild/freebsd-x64": "0.27.0", - "@esbuild/linux-arm": "0.27.0", - "@esbuild/linux-arm64": "0.27.0", - "@esbuild/linux-ia32": "0.27.0", - "@esbuild/linux-loong64": "0.27.0", - "@esbuild/linux-mips64el": "0.27.0", - "@esbuild/linux-ppc64": "0.27.0", - "@esbuild/linux-riscv64": "0.27.0", - "@esbuild/linux-s390x": "0.27.0", - "@esbuild/linux-x64": "0.27.0", - "@esbuild/netbsd-arm64": "0.27.0", - "@esbuild/netbsd-x64": "0.27.0", - "@esbuild/openbsd-arm64": "0.27.0", - "@esbuild/openbsd-x64": "0.27.0", - "@esbuild/openharmony-arm64": "0.27.0", - "@esbuild/sunos-x64": "0.27.0", - "@esbuild/win32-arm64": "0.27.0", - "@esbuild/win32-ia32": "0.27.0", - "@esbuild/win32-x64": "0.27.0" + "@esbuild/aix-ppc64": "0.27.1", + "@esbuild/android-arm": "0.27.1", + "@esbuild/android-arm64": "0.27.1", + "@esbuild/android-x64": "0.27.1", + "@esbuild/darwin-arm64": "0.27.1", + "@esbuild/darwin-x64": "0.27.1", + "@esbuild/freebsd-arm64": "0.27.1", + "@esbuild/freebsd-x64": "0.27.1", + "@esbuild/linux-arm": "0.27.1", + "@esbuild/linux-arm64": "0.27.1", + "@esbuild/linux-ia32": "0.27.1", + "@esbuild/linux-loong64": "0.27.1", + "@esbuild/linux-mips64el": "0.27.1", + "@esbuild/linux-ppc64": "0.27.1", + "@esbuild/linux-riscv64": "0.27.1", + "@esbuild/linux-s390x": "0.27.1", + "@esbuild/linux-x64": "0.27.1", + "@esbuild/netbsd-arm64": "0.27.1", + "@esbuild/netbsd-x64": "0.27.1", + "@esbuild/openbsd-arm64": "0.27.1", + "@esbuild/openbsd-x64": "0.27.1", + "@esbuild/openharmony-arm64": "0.27.1", + "@esbuild/sunos-x64": "0.27.1", + "@esbuild/win32-arm64": "0.27.1", + "@esbuild/win32-ia32": "0.27.1", + "@esbuild/win32-x64": "0.27.1" } }, "node_modules/typescript": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 13b81356c3..f73f6a8c22 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -64,10 +64,10 @@ importers: version: 4.13.4 '@types/node': specifier: ^24.10.1 - version: 24.10.1 + version: 24.10.2 '@vitest/coverage-v8': specifier: ^3.0.0 - version: 3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.10.1)(happy-dom@20.0.10)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)) + version: 3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.10.2)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)) byte-size: specifier: ^9.0.0 version: 9.0.1 @@ -106,19 +106,19 @@ importers: version: 5.9.3 typescript-eslint: specifier: ^8.28.0 - version: 8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) + version: 8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) vite: specifier: ^7.0.0 - version: 7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2) + version: 7.2.6(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2) vite-tsconfig-paths: specifier: ^5.0.0 - version: 5.1.4(typescript@5.9.3)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)) + version: 5.1.4(typescript@5.9.3)(vite@7.2.6(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)) vitest: specifier: ^3.0.0 - version: 3.2.4(@types/debug@4.1.12)(@types/node@24.10.1)(happy-dom@20.0.10)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2) + version: 3.2.4(@types/debug@4.1.12)(@types/node@24.10.2)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2) vitest-fetch-mock: specifier: ^0.4.0 - version: 0.4.5(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.10.1)(happy-dom@20.0.10)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)) + version: 0.4.5(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.10.2)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)) yaml: specifier: ^2.3.1 version: 2.8.2 @@ -215,7 +215,7 @@ importers: version: 3.7.1 '@types/node': specifier: ^24.10.1 - version: 24.10.1 + version: 24.10.2 '@types/oidc-provider': specifier: ^9.0.0 version: 9.5.0 @@ -284,13 +284,13 @@ importers: version: 5.9.3 typescript-eslint: specifier: ^8.28.0 - version: 8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) + version: 8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) utimes: specifier: ^5.2.1 version: 5.2.1(encoding@0.1.13) vitest: specifier: ^3.0.0 - version: 3.2.4(@types/debug@4.1.12)(@types/node@24.10.1)(happy-dom@20.0.10)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2(encoding@0.1.13)))(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2) + version: 3.2.4(@types/debug@4.1.12)(@types/node@24.10.2)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2(encoding@0.1.13)))(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2) open-api/typescript-sdk: dependencies: @@ -300,7 +300,7 @@ importers: devDependencies: '@types/node': specifier: ^24.10.1 - version: 24.10.1 + version: 24.10.2 typescript: specifier: ^5.3.3 version: 5.9.3 @@ -312,7 +312,7 @@ importers: version: 1.1.1 esbuild: specifier: ^0.27.0 - version: 0.27.0 + version: 0.27.1 typescript: specifier: ^5.3.2 version: 5.9.3 @@ -324,7 +324,7 @@ importers: version: 2.0.0-rc13 '@nestjs/bullmq': specifier: ^11.0.1 - version: 11.0.4(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(bullmq@5.65.0) + version: 11.0.4(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(bullmq@5.65.1) '@nestjs/common': specifier: ^11.0.4 version: 11.1.9(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2) @@ -381,10 +381,10 @@ importers: version: 1.38.0 '@react-email/components': specifier: ^0.5.0 - version: 0.5.7(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + version: 0.5.7(react-dom@19.2.1(react@19.2.1))(react@19.2.1) '@react-email/render': specifier: ^1.1.2 - version: 1.4.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + version: 1.4.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) '@socket.io/redis-adapter': specifier: ^8.3.0 version: 8.3.0(socket.io-adapter@2.5.5) @@ -405,7 +405,7 @@ importers: version: 2.2.1 bullmq: specifier: ^5.51.0 - version: 5.65.0 + version: 5.65.1 chokidar: specifier: ^4.0.3 version: 4.0.3 @@ -432,7 +432,7 @@ importers: version: 34.0.0 express: specifier: ^5.1.0 - version: 5.2.0 + version: 5.2.1 fast-glob: specifier: ^3.3.2 version: 3.3.3 @@ -459,7 +459,7 @@ importers: version: 4.1.1 jsonwebtoken: specifier: ^9.0.2 - version: 9.0.2 + version: 9.0.3 kysely: specifier: 0.28.2 version: 0.28.2 @@ -480,7 +480,7 @@ importers: version: 2.0.2 nest-commander: specifier: ^3.16.0 - version: 3.20.1(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(@types/inquirer@8.2.11)(@types/node@24.10.1)(typescript@5.9.3) + version: 3.20.1(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(@types/inquirer@8.2.11)(@types/node@24.10.2)(typescript@5.9.3) nestjs-cls: specifier: ^5.0.0 version: 5.4.3(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(reflect-metadata@0.2.2)(rxjs@7.8.2) @@ -510,10 +510,10 @@ importers: version: 3.4.7 react: specifier: ^19.0.0 - version: 19.2.0 + version: 19.2.1 react-dom: specifier: ^19.0.0 - version: 19.2.0(react@19.2.0) + version: 19.2.1(react@19.2.1) react-email: specifier: ^4.0.0 version: 4.3.2 @@ -562,7 +562,7 @@ importers: version: 9.39.1 '@nestjs/cli': specifier: ^11.0.2 - version: 11.0.13(@swc/core@1.15.3(@swc/helpers@0.5.17))(@types/node@24.10.1) + version: 11.0.14(@swc/core@1.15.3(@swc/helpers@0.5.17))(@types/node@24.10.2) '@nestjs/schematics': specifier: ^11.0.0 version: 11.0.9(chokidar@4.0.3)(typescript@5.9.3) @@ -589,10 +589,10 @@ importers: version: 1.8.1 '@types/cookie-parser': specifier: ^1.4.8 - version: 1.4.10(@types/express@5.0.5) + version: 1.4.10(@types/express@5.0.6) '@types/express': specifier: ^5.0.0 - version: 5.0.5 + version: 5.0.6 '@types/fluent-ffmpeg': specifier: ^2.1.21 version: 2.1.28 @@ -616,7 +616,7 @@ importers: version: 2.0.0 '@types/node': specifier: ^24.10.1 - version: 24.10.1 + version: 24.10.2 '@types/nodemailer': specifier: ^7.0.0 version: 7.0.4 @@ -646,7 +646,7 @@ importers: version: 13.15.10 '@vitest/coverage-v8': specifier: ^3.0.0 - version: 3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.10.1)(happy-dom@20.0.10)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)) + version: 3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.10.2)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)) eslint: specifier: ^9.14.0 version: 9.39.1(jiti@2.6.1) @@ -694,16 +694,16 @@ importers: version: 5.9.3 typescript-eslint: specifier: ^8.28.0 - version: 8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) + version: 8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) unplugin-swc: specifier: ^1.4.5 version: 1.5.9(@swc/core@1.15.3(@swc/helpers@0.5.17))(rollup@4.53.3) vite-tsconfig-paths: specifier: ^5.0.0 - version: 5.1.4(typescript@5.9.3)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)) + version: 5.1.4(typescript@5.9.3)(vite@7.2.6(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)) vitest: specifier: ^3.0.0 - version: 3.2.4(@types/debug@4.1.12)(@types/node@24.10.1)(happy-dom@20.0.10)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2) + version: 3.2.4(@types/debug@4.1.12)(@types/node@24.10.2)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2) web: dependencies: @@ -718,7 +718,7 @@ importers: version: link:../open-api/typescript-sdk '@immich/ui': specifier: ^0.50.1 - version: 0.50.1(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2) + version: 0.50.1(@sveltejs/kit@2.49.1(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.5)(vite@7.2.6(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.5)(vite@7.2.6(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.5) '@mapbox/mapbox-gl-rtl-text': specifier: 0.2.3 version: 0.2.3(mapbox-gl@1.13.3) @@ -751,7 +751,7 @@ importers: version: 0.41.4 '@zoom-image/svelte': specifier: ^0.3.0 - version: 0.3.8(svelte@5.45.2) + version: 0.3.8(svelte@5.45.5) async-mutex: specifier: ^0.5.0 version: 0.5.0 @@ -772,7 +772,7 @@ importers: version: 4.7.8 happy-dom: specifier: ^20.0.0 - version: 20.0.10 + version: 20.0.11 intl-messageformat: specifier: ^10.7.11 version: 10.7.18 @@ -787,7 +787,7 @@ importers: version: 3.7.2 maplibre-gl: specifier: ^5.6.2 - version: 5.13.0 + version: 5.14.0 pmtiles: specifier: ^4.3.0 version: 4.3.0 @@ -805,13 +805,13 @@ importers: version: 5.2.2 svelte-i18n: specifier: ^4.0.1 - version: 4.0.1(svelte@5.45.2) + version: 4.0.1(svelte@5.45.5) svelte-maplibre: specifier: ^1.2.5 - version: 1.2.5(svelte@5.45.2) + version: 1.2.5(svelte@5.45.5) svelte-persisted-store: specifier: ^0.12.0 - version: 0.12.0(svelte@5.45.2) + version: 0.12.0(svelte@5.45.5) tabbable: specifier: ^6.2.0 version: 6.3.0 @@ -836,25 +836,25 @@ importers: version: 3.1.2 '@sveltejs/adapter-static': specifier: ^3.0.8 - version: 3.0.10(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2))) + version: 3.0.10(@sveltejs/kit@2.49.1(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.5)(vite@7.2.6(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.5)(vite@7.2.6(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2))) '@sveltejs/enhanced-img': specifier: ^0.9.0 - version: 0.9.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(rollup@4.53.3)(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)) + version: 0.9.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.5)(vite@7.2.6(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(rollup@4.53.3)(svelte@5.45.5)(vite@7.2.6(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)) '@sveltejs/kit': specifier: ^2.27.1 - version: 2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)) + version: 2.49.1(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.5)(vite@7.2.6(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.5)(vite@7.2.6(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)) '@sveltejs/vite-plugin-svelte': specifier: 6.2.1 - version: 6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)) + version: 6.2.1(svelte@5.45.5)(vite@7.2.6(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)) '@tailwindcss/vite': specifier: ^4.1.7 - version: 4.1.17(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)) + version: 4.1.17(vite@7.2.6(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)) '@testing-library/jest-dom': specifier: ^6.4.2 version: 6.9.1 '@testing-library/svelte': specifier: ^5.2.8 - version: 5.2.9(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2))(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.10.1)(happy-dom@20.0.10)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)) + version: 5.2.9(svelte@5.45.5)(vite@7.2.6(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2))(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.10.2)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)) '@testing-library/user-event': specifier: ^14.5.2 version: 14.6.1(@testing-library/dom@10.4.1) @@ -878,7 +878,7 @@ importers: version: 1.5.6 '@vitest/coverage-v8': specifier: ^3.0.0 - version: 3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.10.1)(happy-dom@20.0.10)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)) + version: 3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.10.2)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)) dotenv: specifier: ^17.0.0 version: 17.2.3 @@ -893,7 +893,7 @@ importers: version: 6.0.2(eslint@9.39.1(jiti@2.6.1)) eslint-plugin-svelte: specifier: ^3.12.4 - version: 3.13.0(eslint@9.39.1(jiti@2.6.1))(svelte@5.45.2) + version: 3.13.1(eslint@9.39.1(jiti@2.6.1))(svelte@5.45.5) eslint-plugin-unicorn: specifier: ^62.0.0 version: 62.0.0(eslint@9.39.1(jiti@2.6.1)) @@ -914,19 +914,19 @@ importers: version: 4.1.1(prettier@3.7.4) prettier-plugin-svelte: specifier: ^3.3.3 - version: 3.4.0(prettier@3.7.4)(svelte@5.45.2) + version: 3.4.0(prettier@3.7.4)(svelte@5.45.5) rollup-plugin-visualizer: specifier: ^6.0.0 version: 6.0.5(rollup@4.53.3) svelte: - specifier: 5.45.2 - version: 5.45.2 + specifier: 5.45.5 + version: 5.45.5 svelte-check: specifier: ^4.1.5 - version: 4.3.4(picomatch@4.0.3)(svelte@5.45.2)(typescript@5.9.3) + version: 4.3.4(picomatch@4.0.3)(svelte@5.45.5)(typescript@5.9.3) svelte-eslint-parser: specifier: ^1.3.3 - version: 1.4.0(svelte@5.45.2) + version: 1.4.1(svelte@5.45.5) tailwindcss: specifier: ^4.1.7 version: 4.1.17 @@ -935,13 +935,13 @@ importers: version: 5.9.3 typescript-eslint: specifier: ^8.45.0 - version: 8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) + version: 8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) vite: specifier: ^7.1.2 - version: 7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2) + version: 7.2.6(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2) vitest: specifier: ^3.0.0 - version: 3.2.4(@types/debug@4.1.12)(@types/node@24.10.1)(happy-dom@20.0.10)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2) + version: 3.2.4(@types/debug@4.1.12)(@types/node@24.10.2)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2) packages: @@ -2287,8 +2287,8 @@ packages: cpu: [ppc64] os: [aix] - '@esbuild/aix-ppc64@0.27.0': - resolution: {integrity: sha512-KuZrd2hRjz01y5JK9mEBSD3Vj3mbCvemhT466rSuJYeE/hjuBrHfjjcjMdTm/sz7au+++sdbJZJmuBwQLuw68A==} + '@esbuild/aix-ppc64@0.27.1': + resolution: {integrity: sha512-HHB50pdsBX6k47S4u5g/CaLjqS3qwaOVE5ILsq64jyzgMhLuCuZ8rGzM9yhsAjfjkbgUPMzZEPa7DAp7yz6vuA==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] @@ -2305,8 +2305,8 @@ packages: cpu: [arm64] os: [android] - '@esbuild/android-arm64@0.27.0': - resolution: {integrity: sha512-CC3vt4+1xZrs97/PKDkl0yN7w8edvU2vZvAFGD16n9F0Cvniy5qvzRXjfO1l94efczkkQE6g1x0i73Qf5uthOQ==} + '@esbuild/android-arm64@0.27.1': + resolution: {integrity: sha512-45fuKmAJpxnQWixOGCrS+ro4Uvb4Re9+UTieUY2f8AEc+t7d4AaZ6eUJ3Hva7dtrxAAWHtlEFsXFMAgNnGU9uQ==} engines: {node: '>=18'} cpu: [arm64] os: [android] @@ -2323,8 +2323,8 @@ packages: cpu: [arm] os: [android] - '@esbuild/android-arm@0.27.0': - resolution: {integrity: sha512-j67aezrPNYWJEOHUNLPj9maeJte7uSMM6gMoxfPC9hOg8N02JuQi/T7ewumf4tNvJadFkvLZMlAq73b9uwdMyQ==} + '@esbuild/android-arm@0.27.1': + resolution: {integrity: sha512-kFqa6/UcaTbGm/NncN9kzVOODjhZW8e+FRdSeypWe6j33gzclHtwlANs26JrupOntlcWmB0u8+8HZo8s7thHvg==} engines: {node: '>=18'} cpu: [arm] os: [android] @@ -2341,8 +2341,8 @@ packages: cpu: [x64] os: [android] - '@esbuild/android-x64@0.27.0': - resolution: {integrity: sha512-wurMkF1nmQajBO1+0CJmcN17U4BP6GqNSROP8t0X/Jiw2ltYGLHpEksp9MpoBqkrFR3kv2/te6Sha26k3+yZ9Q==} + '@esbuild/android-x64@0.27.1': + resolution: {integrity: sha512-LBEpOz0BsgMEeHgenf5aqmn/lLNTFXVfoWMUox8CtWWYK9X4jmQzWjoGoNb8lmAYml/tQ/Ysvm8q7szu7BoxRQ==} engines: {node: '>=18'} cpu: [x64] os: [android] @@ -2359,8 +2359,8 @@ packages: cpu: [arm64] os: [darwin] - '@esbuild/darwin-arm64@0.27.0': - resolution: {integrity: sha512-uJOQKYCcHhg07DL7i8MzjvS2LaP7W7Pn/7uA0B5S1EnqAirJtbyw4yC5jQ5qcFjHK9l6o/MX9QisBg12kNkdHg==} + '@esbuild/darwin-arm64@0.27.1': + resolution: {integrity: sha512-veg7fL8eMSCVKL7IW4pxb54QERtedFDfY/ASrumK/SbFsXnRazxY4YykN/THYqFnFwJ0aVjiUrVG2PwcdAEqQQ==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] @@ -2377,8 +2377,8 @@ packages: cpu: [x64] os: [darwin] - '@esbuild/darwin-x64@0.27.0': - resolution: {integrity: sha512-8mG6arH3yB/4ZXiEnXof5MK72dE6zM9cDvUcPtxhUZsDjESl9JipZYW60C3JGreKCEP+p8P/72r69m4AZGJd5g==} + '@esbuild/darwin-x64@0.27.1': + resolution: {integrity: sha512-+3ELd+nTzhfWb07Vol7EZ+5PTbJ/u74nC6iv4/lwIU99Ip5uuY6QoIf0Hn4m2HoV0qcnRivN3KSqc+FyCHjoVQ==} engines: {node: '>=18'} cpu: [x64] os: [darwin] @@ -2395,8 +2395,8 @@ packages: cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-arm64@0.27.0': - resolution: {integrity: sha512-9FHtyO988CwNMMOE3YIeci+UV+x5Zy8fI2qHNpsEtSF83YPBmE8UWmfYAQg6Ux7Gsmd4FejZqnEUZCMGaNQHQw==} + '@esbuild/freebsd-arm64@0.27.1': + resolution: {integrity: sha512-/8Rfgns4XD9XOSXlzUDepG8PX+AVWHliYlUkFI3K3GB6tqbdjYqdhcb4BKRd7C0BhZSoaCxhv8kTcBrcZWP+xg==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] @@ -2413,8 +2413,8 @@ packages: cpu: [x64] os: [freebsd] - '@esbuild/freebsd-x64@0.27.0': - resolution: {integrity: sha512-zCMeMXI4HS/tXvJz8vWGexpZj2YVtRAihHLk1imZj4efx1BQzN76YFeKqlDr3bUWI26wHwLWPd3rwh6pe4EV7g==} + '@esbuild/freebsd-x64@0.27.1': + resolution: {integrity: sha512-GITpD8dK9C+r+5yRT/UKVT36h/DQLOHdwGVwwoHidlnA168oD3uxA878XloXebK4Ul3gDBBIvEdL7go9gCUFzQ==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] @@ -2431,8 +2431,8 @@ packages: cpu: [arm64] os: [linux] - '@esbuild/linux-arm64@0.27.0': - resolution: {integrity: sha512-AS18v0V+vZiLJyi/4LphvBE+OIX682Pu7ZYNsdUHyUKSoRwdnOsMf6FDekwoAFKej14WAkOef3zAORJgAtXnlQ==} + '@esbuild/linux-arm64@0.27.1': + resolution: {integrity: sha512-W9//kCrh/6in9rWIBdKaMtuTTzNj6jSeG/haWBADqLLa9P8O5YSRDzgD5y9QBok4AYlzS6ARHifAb75V6G670Q==} engines: {node: '>=18'} cpu: [arm64] os: [linux] @@ -2449,8 +2449,8 @@ packages: cpu: [arm] os: [linux] - '@esbuild/linux-arm@0.27.0': - resolution: {integrity: sha512-t76XLQDpxgmq2cNXKTVEB7O7YMb42atj2Re2Haf45HkaUpjM2J0UuJZDuaGbPbamzZ7bawyGFUkodL+zcE+jvQ==} + '@esbuild/linux-arm@0.27.1': + resolution: {integrity: sha512-ieMID0JRZY/ZeCrsFQ3Y3NlHNCqIhTprJfDgSB3/lv5jJZ8FX3hqPyXWhe+gvS5ARMBJ242PM+VNz/ctNj//eA==} engines: {node: '>=18'} cpu: [arm] os: [linux] @@ -2467,8 +2467,8 @@ packages: cpu: [ia32] os: [linux] - '@esbuild/linux-ia32@0.27.0': - resolution: {integrity: sha512-Mz1jxqm/kfgKkc/KLHC5qIujMvnnarD9ra1cEcrs7qshTUSksPihGrWHVG5+osAIQ68577Zpww7SGapmzSt4Nw==} + '@esbuild/linux-ia32@0.27.1': + resolution: {integrity: sha512-VIUV4z8GD8rtSVMfAj1aXFahsi/+tcoXXNYmXgzISL+KB381vbSTNdeZHHHIYqFyXcoEhu9n5cT+05tRv13rlw==} engines: {node: '>=18'} cpu: [ia32] os: [linux] @@ -2485,8 +2485,8 @@ packages: cpu: [loong64] os: [linux] - '@esbuild/linux-loong64@0.27.0': - resolution: {integrity: sha512-QbEREjdJeIreIAbdG2hLU1yXm1uu+LTdzoq1KCo4G4pFOLlvIspBm36QrQOar9LFduavoWX2msNFAAAY9j4BDg==} + '@esbuild/linux-loong64@0.27.1': + resolution: {integrity: sha512-l4rfiiJRN7sTNI//ff65zJ9z8U+k6zcCg0LALU5iEWzY+a1mVZ8iWC1k5EsNKThZ7XCQ6YWtsZ8EWYm7r1UEsg==} engines: {node: '>=18'} cpu: [loong64] os: [linux] @@ -2503,8 +2503,8 @@ packages: cpu: [mips64el] os: [linux] - '@esbuild/linux-mips64el@0.27.0': - resolution: {integrity: sha512-sJz3zRNe4tO2wxvDpH/HYJilb6+2YJxo/ZNbVdtFiKDufzWq4JmKAiHy9iGoLjAV7r/W32VgaHGkk35cUXlNOg==} + '@esbuild/linux-mips64el@0.27.1': + resolution: {integrity: sha512-U0bEuAOLvO/DWFdygTHWY8C067FXz+UbzKgxYhXC0fDieFa0kDIra1FAhsAARRJbvEyso8aAqvPdNxzWuStBnA==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] @@ -2521,8 +2521,8 @@ packages: cpu: [ppc64] os: [linux] - '@esbuild/linux-ppc64@0.27.0': - resolution: {integrity: sha512-z9N10FBD0DCS2dmSABDBb5TLAyF1/ydVb+N4pi88T45efQ/w4ohr/F/QYCkxDPnkhkp6AIpIcQKQ8F0ANoA2JA==} + '@esbuild/linux-ppc64@0.27.1': + resolution: {integrity: sha512-NzdQ/Xwu6vPSf/GkdmRNsOfIeSGnh7muundsWItmBsVpMoNPVpM61qNzAVY3pZ1glzzAxLR40UyYM23eaDDbYQ==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] @@ -2539,8 +2539,8 @@ packages: cpu: [riscv64] os: [linux] - '@esbuild/linux-riscv64@0.27.0': - resolution: {integrity: sha512-pQdyAIZ0BWIC5GyvVFn5awDiO14TkT/19FTmFcPdDec94KJ1uZcmFs21Fo8auMXzD4Tt+diXu1LW1gHus9fhFQ==} + '@esbuild/linux-riscv64@0.27.1': + resolution: {integrity: sha512-7zlw8p3IApcsN7mFw0O1Z1PyEk6PlKMu18roImfl3iQHTnr/yAfYv6s4hXPidbDoI2Q0pW+5xeoM4eTCC0UdrQ==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] @@ -2557,8 +2557,8 @@ packages: cpu: [s390x] os: [linux] - '@esbuild/linux-s390x@0.27.0': - resolution: {integrity: sha512-hPlRWR4eIDDEci953RI1BLZitgi5uqcsjKMxwYfmi4LcwyWo2IcRP+lThVnKjNtk90pLS8nKdroXYOqW+QQH+w==} + '@esbuild/linux-s390x@0.27.1': + resolution: {integrity: sha512-cGj5wli+G+nkVQdZo3+7FDKC25Uh4ZVwOAK6A06Hsvgr8WqBBuOy/1s+PUEd/6Je+vjfm6stX0kmib5b/O2Ykw==} engines: {node: '>=18'} cpu: [s390x] os: [linux] @@ -2575,8 +2575,8 @@ packages: cpu: [x64] os: [linux] - '@esbuild/linux-x64@0.27.0': - resolution: {integrity: sha512-1hBWx4OUJE2cab++aVZ7pObD6s+DK4mPGpemtnAORBvb5l/g5xFGk0vc0PjSkrDs0XaXj9yyob3d14XqvnQ4gw==} + '@esbuild/linux-x64@0.27.1': + resolution: {integrity: sha512-z3H/HYI9MM0HTv3hQZ81f+AKb+yEoCRlUby1F80vbQ5XdzEMyY/9iNlAmhqiBKw4MJXwfgsh7ERGEOhrM1niMA==} engines: {node: '>=18'} cpu: [x64] os: [linux] @@ -2587,8 +2587,8 @@ packages: cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-arm64@0.27.0': - resolution: {integrity: sha512-6m0sfQfxfQfy1qRuecMkJlf1cIzTOgyaeXaiVaaki8/v+WB+U4hc6ik15ZW6TAllRlg/WuQXxWj1jx6C+dfy3w==} + '@esbuild/netbsd-arm64@0.27.1': + resolution: {integrity: sha512-wzC24DxAvk8Em01YmVXyjl96Mr+ecTPyOuADAvjGg+fyBpGmxmcr2E5ttf7Im8D0sXZihpxzO1isus8MdjMCXQ==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] @@ -2605,8 +2605,8 @@ packages: cpu: [x64] os: [netbsd] - '@esbuild/netbsd-x64@0.27.0': - resolution: {integrity: sha512-xbbOdfn06FtcJ9d0ShxxvSn2iUsGd/lgPIO2V3VZIPDbEaIj1/3nBBe1AwuEZKXVXkMmpr6LUAgMkLD/4D2PPA==} + '@esbuild/netbsd-x64@0.27.1': + resolution: {integrity: sha512-1YQ8ybGi2yIXswu6eNzJsrYIGFpnlzEWRl6iR5gMgmsrR0FcNoV1m9k9sc3PuP5rUBLshOZylc9nqSgymI+TYg==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] @@ -2617,8 +2617,8 @@ packages: cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-arm64@0.27.0': - resolution: {integrity: sha512-fWgqR8uNbCQ/GGv0yhzttj6sU/9Z5/Sv/VGU3F5OuXK6J6SlriONKrQ7tNlwBrJZXRYk5jUhuWvF7GYzGguBZQ==} + '@esbuild/openbsd-arm64@0.27.1': + resolution: {integrity: sha512-5Z+DzLCrq5wmU7RDaMDe2DVXMRm2tTDvX2KU14JJVBN2CT/qov7XVix85QoJqHltpvAOZUAc3ndU56HSMWrv8g==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] @@ -2635,8 +2635,8 @@ packages: cpu: [x64] os: [openbsd] - '@esbuild/openbsd-x64@0.27.0': - resolution: {integrity: sha512-aCwlRdSNMNxkGGqQajMUza6uXzR/U0dIl1QmLjPtRbLOx3Gy3otfFu/VjATy4yQzo9yFDGTxYDo1FfAD9oRD2A==} + '@esbuild/openbsd-x64@0.27.1': + resolution: {integrity: sha512-Q73ENzIdPF5jap4wqLtsfh8YbYSZ8Q0wnxplOlZUOyZy7B4ZKW8DXGWgTCZmF8VWD7Tciwv5F4NsRf6vYlZtqg==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] @@ -2647,8 +2647,8 @@ packages: cpu: [arm64] os: [openharmony] - '@esbuild/openharmony-arm64@0.27.0': - resolution: {integrity: sha512-nyvsBccxNAsNYz2jVFYwEGuRRomqZ149A39SHWk4hV0jWxKM0hjBPm3AmdxcbHiFLbBSwG6SbpIcUbXjgyECfA==} + '@esbuild/openharmony-arm64@0.27.1': + resolution: {integrity: sha512-ajbHrGM/XiK+sXM0JzEbJAen+0E+JMQZ2l4RR4VFwvV9JEERx+oxtgkpoKv1SevhjavK2z2ReHk32pjzktWbGg==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] @@ -2665,8 +2665,8 @@ packages: cpu: [x64] os: [sunos] - '@esbuild/sunos-x64@0.27.0': - resolution: {integrity: sha512-Q1KY1iJafM+UX6CFEL+F4HRTgygmEW568YMqDA5UV97AuZSm21b7SXIrRJDwXWPzr8MGr75fUZPV67FdtMHlHA==} + '@esbuild/sunos-x64@0.27.1': + resolution: {integrity: sha512-IPUW+y4VIjuDVn+OMzHc5FV4GubIwPnsz6ubkvN8cuhEqH81NovB53IUlrlBkPMEPxvNnf79MGBoz8rZ2iW8HA==} engines: {node: '>=18'} cpu: [x64] os: [sunos] @@ -2683,8 +2683,8 @@ packages: cpu: [arm64] os: [win32] - '@esbuild/win32-arm64@0.27.0': - resolution: {integrity: sha512-W1eyGNi6d+8kOmZIwi/EDjrL9nxQIQ0MiGqe/AWc6+IaHloxHSGoeRgDRKHFISThLmsewZ5nHFvGFWdBYlgKPg==} + '@esbuild/win32-arm64@0.27.1': + resolution: {integrity: sha512-RIVRWiljWA6CdVu8zkWcRmGP7iRRIIwvhDKem8UMBjPql2TXM5PkDVvvrzMtj1V+WFPB4K7zkIGM7VzRtFkjdg==} engines: {node: '>=18'} cpu: [arm64] os: [win32] @@ -2701,8 +2701,8 @@ packages: cpu: [ia32] os: [win32] - '@esbuild/win32-ia32@0.27.0': - resolution: {integrity: sha512-30z1aKL9h22kQhilnYkORFYt+3wp7yZsHWus+wSKAJR8JtdfI76LJ4SBdMsCopTR3z/ORqVu5L1vtnHZWVj4cQ==} + '@esbuild/win32-ia32@0.27.1': + resolution: {integrity: sha512-2BR5M8CPbptC1AK5JbJT1fWrHLvejwZidKx3UMSF0ecHMa+smhi16drIrCEggkgviBwLYd5nwrFLSl5Kho96RQ==} engines: {node: '>=18'} cpu: [ia32] os: [win32] @@ -2719,8 +2719,8 @@ packages: cpu: [x64] os: [win32] - '@esbuild/win32-x64@0.27.0': - resolution: {integrity: sha512-aIitBcjQeyOhMTImhLZmtxfdOcuNRpwlPNmlFKPcHQYPhEssw75Cl1TSXJXpMkzaua9FUetx/4OQKq7eJul5Cg==} + '@esbuild/win32-x64@0.27.1': + resolution: {integrity: sha512-d5X6RMYv6taIymSk8JBP+nxv8DQAMY6A51GPgusqLdK9wBz5wWIXy1KjTck6HnjE9hqJzJRdk+1p/t5soSbCtw==} engines: {node: '>=18'} cpu: [x64] os: [win32] @@ -3305,11 +3305,11 @@ packages: resolution: {integrity: sha512-TUM5JD40H2mgtVXl5IwWz03BuQabw8oZQLJTmPpJA0YTYF+B+oZppy5lNMO6bMvHzB+/5mxqW9VLG3wFdeqtOw==} hasBin: true - '@maplibre/mlt@1.1.0': - resolution: {integrity: sha512-anR8WxKIgZUJQLlZtID0v06wd9Q//9K/6lLLU3dOzmeO/xLEzAwmEqP24jEnEUBcnZGkM4vidz9H6Q4guNAAlw==} + '@maplibre/mlt@1.1.2': + resolution: {integrity: sha512-SQKdJ909VGROkA6ovJgtHNs9YXV4YXUPS+VaZ50I2Mt951SLlUm2Cv34x5Xwc1HiFlsd3h2Yrs5cn7xzqBmENw==} - '@maplibre/vt-pbf@4.0.3': - resolution: {integrity: sha512-YsW99BwnT+ukJRkseBcLuZHfITB4puJoxnqPVjo72rhW/TaawVYsgQHcqWLzTxqknttYoDpgyERzWSa/XrETdA==} + '@maplibre/vt-pbf@4.1.0': + resolution: {integrity: sha512-9LjFAoWtxdGRns8RK9vG3Fcw/fb3eHMxvAn2jffwn3jnVO1k49VOv6+FEza70rK7WzF8GnBiKa0K39RyfevKUw==} '@mdi/js@7.4.47': resolution: {integrity: sha512-KPnNOtm5i2pMabqZxpUz7iQf+mfrYZyKCZ8QNz85czgEt7cuHcGorWfdzUMWYA0SD+a6Hn4FmJ+YhzzzjkTZrQ==} @@ -3381,8 +3381,8 @@ packages: '@nestjs/core': ^10.0.0 || ^11.0.0 bullmq: ^3.0.0 || ^4.0.0 || ^5.0.0 - '@nestjs/cli@11.0.13': - resolution: {integrity: sha512-cPDDahP/IPDcqraIHjUZQXPupYQDoZlBRFOjgM76vVfcVnWQ45TUaat0tgR2ptfeQG0gY2kwtm4/JYwe5xu/8A==} + '@nestjs/cli@11.0.14': + resolution: {integrity: sha512-YwP03zb5VETTwelXU+AIzMVbEZKk/uxJL+z9pw0mdG9ogAtqZ6/mpmIM4nEq/NU8D0a7CBRLcMYUmWW/55pfqw==} engines: {node: '>= 20.11'} hasBin: true peerDependencies: @@ -4312,8 +4312,8 @@ packages: svelte: ^5.0.0 vite: ^6.3.0 || >=7.0.0 - '@sveltejs/kit@2.49.0': - resolution: {integrity: sha512-oH8tXw7EZnie8FdOWYrF7Yn4IKrqTFHhXvl8YxXxbKwTMcD/5NNCryUSEXRk2ZR4ojnub0P8rNrsVGHXWqIDtA==} + '@sveltejs/kit@2.49.1': + resolution: {integrity: sha512-vByReCTTdlNM80vva8alAQC80HcOiHLkd8XAxIiKghKSHcqeNfyhp3VsYAV8VSiPKu4Jc8wWCfsZNAIvd1uCqA==} engines: {node: '>=18.13'} hasBin: true peerDependencies: @@ -4745,8 +4745,8 @@ packages: '@types/express@4.17.25': resolution: {integrity: sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==} - '@types/express@5.0.5': - resolution: {integrity: sha512-LuIQOcb6UmnF7C1PCFmEU1u2hmiHL43fgFQX67sN3H4Z+0Yk0Neo++mFsBjhOAuLzvlQeqAAkeDOZrJs9rzumQ==} + '@types/express@5.0.6': + resolution: {integrity: sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==} '@types/filesystem@0.0.36': resolution: {integrity: sha512-vPDXOZuannb9FZdxgHnqSwAG/jvdGM8Wq+6N4D/d80z+D4HWH+bItqsZaVRQykAn6WEVeEkLm2oQigyHtgb0RA==} @@ -4871,11 +4871,11 @@ packages: '@types/node@18.19.130': resolution: {integrity: sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==} - '@types/node@20.19.24': - resolution: {integrity: sha512-FE5u0ezmi6y9OZEzlJfg37mqqf6ZDSF2V/NLjUyGrR9uTZ7Sb9F7bLNZ03S4XVUNRWGA7Ck4c1kK+YnuWjl+DA==} + '@types/node@20.19.26': + resolution: {integrity: sha512-0l6cjgF0XnihUpndDhk+nyD3exio3iKaYROSgvh/qSevPXax3L8p5DBRFjbvalnwatGgHEQn2R88y2fA3g4irg==} - '@types/node@24.10.1': - resolution: {integrity: sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==} + '@types/node@24.10.2': + resolution: {integrity: sha512-WOhQTZ4G8xZ1tjJTvKOpyEVSGgOTvJAfDK3FNFgELyaTpzhdgHVHeqW8V+UJvzF5BT+/B54T/1S2K6gd9c7bbA==} '@types/nodemailer@7.0.4': resolution: {integrity: sha512-ee8fxWqOchH+Hv6MDDNNy028kwvVnLplrStm4Zf/3uHWw5zzo8FoYYeffpJtGs2wWysEumMH0ZIdMGMY1eMAow==} @@ -4949,6 +4949,9 @@ packages: '@types/serve-static@1.15.10': resolution: {integrity: sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==} + '@types/serve-static@2.2.0': + resolution: {integrity: sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==} + '@types/sockjs@0.3.36': resolution: {integrity: sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==} @@ -4997,63 +5000,63 @@ packages: '@types/yargs@17.0.34': resolution: {integrity: sha512-KExbHVa92aJpw9WDQvzBaGVE2/Pz+pLZQloT2hjL8IqsZnV62rlPOYvNnLmf/L2dyllfVUOVBj64M0z/46eR2A==} - '@typescript-eslint/eslint-plugin@8.48.0': - resolution: {integrity: sha512-XxXP5tL1txl13YFtrECECQYeZjBZad4fyd3cFV4a19LkAY/bIp9fev3US4S5fDVV2JaYFiKAZ/GRTOLer+mbyQ==} + '@typescript-eslint/eslint-plugin@8.48.1': + resolution: {integrity: sha512-X63hI1bxl5ohelzr0LY5coufyl0LJNthld+abwxpCoo6Gq+hSqhKwci7MUWkXo67mzgUK6YFByhmaHmUcuBJmA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.48.0 + '@typescript-eslint/parser': ^8.48.1 eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/parser@8.48.0': - resolution: {integrity: sha512-jCzKdm/QK0Kg4V4IK/oMlRZlY+QOcdjv89U2NgKHZk1CYTj82/RVSx1mV/0gqCVMJ/DA+Zf/S4NBWNF8GQ+eqQ==} + '@typescript-eslint/parser@8.48.1': + resolution: {integrity: sha512-PC0PDZfJg8sP7cmKe6L3QIL8GZwU5aRvUFedqSIpw3B+QjRSUZeeITC2M5XKeMXEzL6wccN196iy3JLwKNvDVA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/project-service@8.48.0': - resolution: {integrity: sha512-Ne4CTZyRh1BecBf84siv42wv5vQvVmgtk8AuiEffKTUo3DrBaGYZueJSxxBZ8fjk/N3DrgChH4TOdIOwOwiqqw==} + '@typescript-eslint/project-service@8.48.1': + resolution: {integrity: sha512-HQWSicah4s9z2/HifRPQ6b6R7G+SBx64JlFQpgSSHWPKdvCZX57XCbszg/bapbRsOEv42q5tayTYcEFpACcX1w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/scope-manager@8.48.0': - resolution: {integrity: sha512-uGSSsbrtJrLduti0Q1Q9+BF1/iFKaxGoQwjWOIVNJv0o6omrdyR8ct37m4xIl5Zzpkp69Kkmvom7QFTtue89YQ==} + '@typescript-eslint/scope-manager@8.48.1': + resolution: {integrity: sha512-rj4vWQsytQbLxC5Bf4XwZ0/CKd362DkWMUkviT7DCS057SK64D5lH74sSGzhI6PDD2HCEq02xAP9cX68dYyg1w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.48.0': - resolution: {integrity: sha512-WNebjBdFdyu10sR1M4OXTt2OkMd5KWIL+LLfeH9KhgP+jzfDV/LI3eXzwJ1s9+Yc0Kzo2fQCdY/OpdusCMmh6w==} + '@typescript-eslint/tsconfig-utils@8.48.1': + resolution: {integrity: sha512-k0Jhs4CpEffIBm6wPaCXBAD7jxBtrHjrSgtfCjUvPp9AZ78lXKdTR8fxyZO5y4vWNlOvYXRtngSZNSn+H53Jkw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/type-utils@8.48.0': - resolution: {integrity: sha512-zbeVaVqeXhhab6QNEKfK96Xyc7UQuoFWERhEnj3mLVnUWrQnv15cJNseUni7f3g557gm0e46LZ6IJ4NJVOgOpw==} + '@typescript-eslint/type-utils@8.48.1': + resolution: {integrity: sha512-1jEop81a3LrJQLTf/1VfPQdhIY4PlGDBc/i67EVWObrtvcziysbLN3oReexHOM6N3jyXgCrkBsZpqwH0hiDOQg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/types@8.48.0': - resolution: {integrity: sha512-cQMcGQQH7kwKoVswD1xdOytxQR60MWKM1di26xSUtxehaDs/32Zpqsu5WJlXTtTTqyAVK8R7hvsUnIXRS+bjvA==} + '@typescript-eslint/types@8.48.1': + resolution: {integrity: sha512-+fZ3LZNeiELGmimrujsDCT4CRIbq5oXdHe7chLiW8qzqyPMnn1puNstCrMNVAqwcl2FdIxkuJ4tOs/RFDBVc/Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.48.0': - resolution: {integrity: sha512-ljHab1CSO4rGrQIAyizUS6UGHHCiAYhbfcIZ1zVJr5nMryxlXMVWS3duFPSKvSUbFPwkXMFk1k0EMIjub4sRRQ==} + '@typescript-eslint/typescript-estree@8.48.1': + resolution: {integrity: sha512-/9wQ4PqaefTK6POVTjJaYS0bynCgzh6ClJHGSBj06XEHjkfylzB+A3qvyaXnErEZSaxhIo4YdyBgq6j4RysxDg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/utils@8.48.0': - resolution: {integrity: sha512-yTJO1XuGxCsSfIVt1+1UrLHtue8xz16V8apzPYI06W0HbEbEWHxHXgZaAgavIkoh+GeV6hKKd5jm0sS6OYxWXQ==} + '@typescript-eslint/utils@8.48.1': + resolution: {integrity: sha512-fAnhLrDjiVfey5wwFRwrweyRlCmdz5ZxXz2G/4cLn0YDLjTapmN4gcCsTBR1N2rWnZSDeWpYtgLDsJt+FpmcwA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/visitor-keys@8.48.0': - resolution: {integrity: sha512-T0XJMaRPOH3+LBbAfzR2jalckP1MSG/L9eUtY0DEzUyVaXJ/t6zN0nR7co5kz0Jko/nkSYCBRkz1djvjajVTTg==} + '@typescript-eslint/visitor-keys@8.48.1': + resolution: {integrity: sha512-BmxxndzEWhE4TIEEMBs8lP3MBWN3jFPs/p6gPm/wkv02o41hI6cq9AuSmGAaTTHPtA1FTi2jBre4A9rm5ZmX+Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@ungap/structured-clone@1.3.0': @@ -5602,8 +5605,8 @@ packages: resolution: {integrity: sha512-bkXY9WsVpY7CvMhKSR6pZilZu9Ln5WDrKVBUXf2S443etkmEO4V58heTecXcUIsNsi4Rx8JUO4NfX1IcQl4deg==} engines: {node: '>=18.20'} - bullmq@5.65.0: - resolution: {integrity: sha512-fyOcyf2ad4zrNmE18vdF/ie7DrW0TwhLt5e0DkqDxbRpDNiUdYqgp2QZJW2ntnUN08T2mDMC4deUUhF2UOAmeQ==} + bullmq@5.65.1: + resolution: {integrity: sha512-QgDAzX1G9L5IRy4Orva5CfQTXZT+5K+OfO/kbPrAqN+pmL9LJekCzxijXehlm/u2eXfWPfWvIdJJIqiuz3WJSg==} bundle-name@4.1.0: resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} @@ -6391,8 +6394,8 @@ packages: engines: {node: '>= 4.0.0'} hasBin: true - devalue@5.5.0: - resolution: {integrity: sha512-69sM5yrHfFLJt0AZ9QqZXGCPfJ7fQjvpln3Rq5+PS03LD32Ost1Q9N+eEnaQwGRIriKkMImXD56ocjQmfjbV3w==} + devalue@5.6.0: + resolution: {integrity: sha512-BaD1s81TFFqbD6Uknni42TrolvEWA1Ih5L+OiHWmi4OYMJVwAYPGtha61I9KxTf52OvVHozHyjPu8zljqdF3uA==} devlop@1.1.0: resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} @@ -6641,8 +6644,8 @@ packages: engines: {node: '>=18'} hasBin: true - esbuild@0.27.0: - resolution: {integrity: sha512-jd0f4NHbD6cALCyGElNpGAOtWxSq46l9X/sWB0Nzd5er4Kz2YTm+Vl0qKFT9KUJvD8+fiO8AvoHhFvEatfVixA==} + esbuild@0.27.1: + resolution: {integrity: sha512-yY35KZckJJuVVPXpvjgxiCuVEJT67F6zDeVTv4rizyPrfGBUpZQsvmxnN+C371c2esD/hNMjj4tpBhuueLN7aA==} engines: {node: '>=18'} hasBin: true @@ -6700,8 +6703,8 @@ packages: eslint-config-prettier: optional: true - eslint-plugin-svelte@3.13.0: - resolution: {integrity: sha512-2ohCCQJJTNbIpQCSDSTWj+FN0OVfPmSO03lmSNT7ytqMaWF6kpT86LdzDqtm4sh7TVPl/OEWJ/d7R87bXP2Vjg==} + eslint-plugin-svelte@3.13.1: + resolution: {integrity: sha512-Ng+kV/qGS8P/isbNYVE3sJORtubB+yLEcYICMkUWNaDTb0SwZni/JhAYXh/Dz/q2eThUwWY0VMPZ//KYD1n3eQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.1 || ^9.0.0 @@ -6762,8 +6765,8 @@ packages: resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} engines: {node: '>=0.10'} - esrap@2.2.0: - resolution: {integrity: sha512-WBmtxe7R9C5mvL4n2le8nMUe4mD5V9oiK2vJpQ9I3y20ENPUomPcphBXE8D1x/Bm84oN1V+lOfgXxtqmxTp3Xg==} + esrap@2.2.1: + resolution: {integrity: sha512-GiYWG34AN/4CUyaWAgunGt0Rxvr1PTMlGC0vvEov/uOQYWne2bpN03Um+k8jT+q3op33mKouP2zeJ6OlM+qeUg==} esrecurse@4.3.0: resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} @@ -6877,8 +6880,8 @@ packages: resolution: {integrity: sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==} engines: {node: '>= 18'} - express@5.2.0: - resolution: {integrity: sha512-XdpJDLxfztVY59X0zPI6sibRiGcxhTPXRD3IhJmjKf2jwMvkRGV1j7loB8U+heeamoU3XvihAaGRTR4aXXUN3A==} + express@5.2.1: + resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} engines: {node: '>= 18'} exsolve@1.0.7: @@ -7284,8 +7287,8 @@ packages: engines: {node: '>=0.4.7'} hasBin: true - happy-dom@20.0.10: - resolution: {integrity: sha512-6umCCHcjQrhP5oXhrHQQvLB0bwb1UzHAHdsXy+FjtKoYjUhmNZsQL8NivwM1vDvNEChJabVrUYxUnp/ZdYmy2g==} + happy-dom@20.0.11: + resolution: {integrity: sha512-QsCdAUHAmiDeKeaNojb1OHOPF7NjcWPBR7obdu3NwH2a/oyQaLg5d0aaCy/9My6CdPChYF07dvz5chaXBGaD4g==} engines: {node: '>=20.0.0'} has-flag@4.0.0: @@ -7949,8 +7952,8 @@ packages: jsonfile@6.2.0: resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} - jsonwebtoken@9.0.2: - resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==} + jsonwebtoken@9.0.3: + resolution: {integrity: sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==} engines: {node: '>=12', npm: '>=6'} just-compare@2.3.0: @@ -7959,11 +7962,11 @@ packages: justified-layout@4.1.0: resolution: {integrity: sha512-M5FimNMXgiOYerVRGsXZ2YK9YNCaTtwtYp7Hb2308U1Q9TXXHx5G0p08mcVR5O53qf8bWY4NJcPBxE6zuayXSg==} - jwa@1.4.2: - resolution: {integrity: sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==} + jwa@2.0.1: + resolution: {integrity: sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==} - jws@3.2.2: - resolution: {integrity: sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==} + jws@4.0.1: + resolution: {integrity: sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==} kdbush@3.0.0: resolution: {integrity: sha512-hRkd6/XW4HTsA9vjVpY9tuXJYLSlelnkTmVFu4M9/7MIYQtFcHpbugAU7UbOfjOiVSVYl2fqgBuJ32JUmRo5Ew==} @@ -8284,8 +8287,8 @@ packages: resolution: {integrity: sha512-p8lJFEiqmEQlyv+DQxFAOG/XPWN0Wp7j/Psq93Zywz7qt9CcUKFYDBOoOEKzqe6gudHVJY8/Bhqw6VDpX2lSBg==} engines: {node: '>=6.4.0'} - maplibre-gl@5.13.0: - resolution: {integrity: sha512-UsIVP34rZdM4TjrjhwBAhbC3HT7AzFx9p/draiAPlLr8/THozZF6WmJnZ9ck4q94uO55z7P7zoGCh+AZVoagsQ==} + maplibre-gl@5.14.0: + resolution: {integrity: sha512-O2ok6N/bQ9NA9nJ22r/PRQQYkUe9JwfDMjBPkQ+8OwsVH4TpA5skIAM2wc0k+rni5lVbAVONVyBvgi1rF2vEPA==} engines: {node: '>=16.14.0', npm: '>=8.1.0'} mark.js@8.11.1: @@ -8909,8 +8912,8 @@ packages: peerDependencies: webpack: ^4.0.0 || ^5.0.0 - nwsapi@2.2.22: - resolution: {integrity: sha512-ujSMe1OWVn55euT1ihwCI1ZcAaAU3nxUiDwfDQldc51ZXaB9m2AyOn6/jh1BLe2t/G8xd6uKG1UBF2aZJeg2SQ==} + nwsapi@2.2.23: + resolution: {integrity: sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==} nypm@0.6.0: resolution: {integrity: sha512-mn8wBFV9G9+UFHIrq+pZ2r2zL4aPau/by3kJb3cM7+5tQHMt6HGQB8FDIeKFYp8o0D2pnH6nVsO88N4AmUxIWg==} @@ -9675,8 +9678,8 @@ packages: resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} engines: {node: '>=4'} - postcss-selector-parser@7.1.0: - resolution: {integrity: sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==} + postcss-selector-parser@7.1.1: + resolution: {integrity: sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==} engines: {node: '>=4'} postcss-sort-media-queries@5.2.0: @@ -9930,10 +9933,10 @@ packages: peerDependencies: react: ^18.3.1 - react-dom@19.2.0: - resolution: {integrity: sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==} + react-dom@19.2.1: + resolution: {integrity: sha512-ibrK8llX2a4eOskq1mXKu/TGZj9qzomO+sNfO98M6d9zIPOEhlBkMkBUBLd1vgS0gQsLDBzA+8jJBVXDnfHmJg==} peerDependencies: - react: ^19.2.0 + react: ^19.2.1 react-email@4.3.2: resolution: {integrity: sha512-WaZcnv9OAIRULY236zDRdk+8r511ooJGH5UOb7FnVsV33hGPI+l5aIZ6drVjXi4QrlLTmLm8PsYvmXRSv31MPA==} @@ -9985,8 +9988,8 @@ packages: resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} engines: {node: '>=0.10.0'} - react@19.2.0: - resolution: {integrity: sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==} + react@19.2.1: + resolution: {integrity: sha512-DGrYcCWK7tvYMnWh79yrPHt+vdx9tY+1gPZa7nJQtO/p8bLTDaHp4dzwEhQB7pZ4Xe3ok4XKuEPrVuc+wlpkmw==} engines: {node: '>=0.10.0'} read-cache@1.0.0: @@ -10727,9 +10730,9 @@ packages: svelte: ^4.0.0 || ^5.0.0-next.0 typescript: '>=5.0.0' - svelte-eslint-parser@1.4.0: - resolution: {integrity: sha512-fjPzOfipR5S7gQ/JvI9r2H8y9gMGXO3JtmrylHLLyahEMquXI0lrebcjT+9/hNgDej0H7abTyox5HpHmW1PSWA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0, pnpm: 10.18.3} + svelte-eslint-parser@1.4.1: + resolution: {integrity: sha512-1eqkfQ93goAhjAXxZiu1SaKI9+0/sxp4JIWQwUpsz7ybehRE5L8dNuz7Iry7K22R47p5/+s9EM+38nHV2OlgXA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0, pnpm: 10.24.0} peerDependencies: svelte: ^3.37.0 || ^4.0.0 || ^5.0.0 peerDependenciesMeta: @@ -10781,8 +10784,8 @@ packages: peerDependencies: svelte: ^5.30.2 - svelte@5.45.2: - resolution: {integrity: sha512-yyXdW2u3H0H/zxxWoGwJoQlRgaSJLp+Vhktv12iRw2WRDlKqUPT54Fi0K/PkXqrdkcQ98aBazpy0AH4BCBVfoA==} + svelte@5.45.5: + resolution: {integrity: sha512-2074U+vObO5Zs8/qhxtBwdi6ZXNIhEBTzNmUFjiZexLxTdt9vq96D/0pnQELl6YcpLMD7pZ2dhXKByfGS8SAdg==} engines: {node: '>=18'} svg-parser@2.0.4: @@ -11129,8 +11132,8 @@ packages: typedarray@0.0.6: resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} - typescript-eslint@8.48.0: - resolution: {integrity: sha512-fcKOvQD9GUn3Xw63EgiDqhvWJ5jsyZUaekl3KVpGsDJnN46WJTe3jWxtQP9lMZm1LJNkFLlTaWAxK2vUQR+cqw==} + typescript-eslint@8.48.1: + resolution: {integrity: sha512-FbOKN1fqNoXp1hIl5KYpObVrp0mCn+CLgn479nmu2IsRMrx2vyv74MmsBLVlhg8qVwNFGbXSp8fh1zp8pEoC2A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 @@ -11390,8 +11393,8 @@ packages: vite: optional: true - vite@7.2.4: - resolution: {integrity: sha512-NL8jTlbo0Tn4dUEXEsUg8KeyG/Lkmc4Fnzb8JXN/Ykm9G4HNImjtABMJgkQoVjOBN/j2WAwDTRytdqJbZsah7w==} + vite@7.2.6: + resolution: {integrity: sha512-tI2l/nFHC5rLh7+5+o7QjKjSR04ivXDF4jcgV0f/bTQ+OJiITy5S6gaynVsEM+7RqzufMnVbIon6Sr5x1SDYaQ==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -11970,11 +11973,11 @@ snapshots: optionalDependencies: chokidar: 4.0.3 - '@angular-devkit/schematics-cli@19.2.19(@types/node@24.10.1)(chokidar@4.0.3)': + '@angular-devkit/schematics-cli@19.2.19(@types/node@24.10.2)(chokidar@4.0.3)': dependencies: '@angular-devkit/core': 19.2.19(chokidar@4.0.3) '@angular-devkit/schematics': 19.2.19(chokidar@4.0.3) - '@inquirer/prompts': 7.3.2(@types/node@24.10.1) + '@inquirer/prompts': 7.3.2(@types/node@24.10.2) ansi-colors: 4.1.3 symbol-observable: 4.0.0 yargs-parser: 21.1.1 @@ -13212,9 +13215,9 @@ snapshots: '@csstools/postcss-cascade-layers@5.0.2(postcss@8.5.6)': dependencies: - '@csstools/selector-specificity': 5.0.0(postcss-selector-parser@7.1.0) + '@csstools/selector-specificity': 5.0.0(postcss-selector-parser@7.1.1) postcss: 8.5.6 - postcss-selector-parser: 7.1.0 + postcss-selector-parser: 7.1.1 '@csstools/postcss-color-function-display-p3-linear@1.0.1(postcss@8.5.6)': dependencies: @@ -13320,9 +13323,9 @@ snapshots: '@csstools/postcss-is-pseudo-class@5.0.3(postcss@8.5.6)': dependencies: - '@csstools/selector-specificity': 5.0.0(postcss-selector-parser@7.1.0) + '@csstools/selector-specificity': 5.0.0(postcss-selector-parser@7.1.1) postcss: 8.5.6 - postcss-selector-parser: 7.1.0 + postcss-selector-parser: 7.1.1 '@csstools/postcss-light-dark-function@2.0.11(postcss@8.5.6)': dependencies: @@ -13414,7 +13417,7 @@ snapshots: '@csstools/postcss-scope-pseudo-class@4.0.1(postcss@8.5.6)': dependencies: postcss: 8.5.6 - postcss-selector-parser: 7.1.0 + postcss-selector-parser: 7.1.1 '@csstools/postcss-sign-functions@1.1.4(postcss@8.5.6)': dependencies: @@ -13447,13 +13450,13 @@ snapshots: dependencies: postcss: 8.5.6 - '@csstools/selector-resolve-nested@3.1.0(postcss-selector-parser@7.1.0)': + '@csstools/selector-resolve-nested@3.1.0(postcss-selector-parser@7.1.1)': dependencies: - postcss-selector-parser: 7.1.0 + postcss-selector-parser: 7.1.1 - '@csstools/selector-specificity@5.0.0(postcss-selector-parser@7.1.0)': + '@csstools/selector-specificity@5.0.0(postcss-selector-parser@7.1.1)': dependencies: - postcss-selector-parser: 7.1.0 + postcss-selector-parser: 7.1.1 '@csstools/utilities@2.0.0(postcss@8.5.6)': dependencies: @@ -14242,7 +14245,7 @@ snapshots: '@esbuild/aix-ppc64@0.25.12': optional: true - '@esbuild/aix-ppc64@0.27.0': + '@esbuild/aix-ppc64@0.27.1': optional: true '@esbuild/android-arm64@0.19.12': @@ -14251,7 +14254,7 @@ snapshots: '@esbuild/android-arm64@0.25.12': optional: true - '@esbuild/android-arm64@0.27.0': + '@esbuild/android-arm64@0.27.1': optional: true '@esbuild/android-arm@0.19.12': @@ -14260,7 +14263,7 @@ snapshots: '@esbuild/android-arm@0.25.12': optional: true - '@esbuild/android-arm@0.27.0': + '@esbuild/android-arm@0.27.1': optional: true '@esbuild/android-x64@0.19.12': @@ -14269,7 +14272,7 @@ snapshots: '@esbuild/android-x64@0.25.12': optional: true - '@esbuild/android-x64@0.27.0': + '@esbuild/android-x64@0.27.1': optional: true '@esbuild/darwin-arm64@0.19.12': @@ -14278,7 +14281,7 @@ snapshots: '@esbuild/darwin-arm64@0.25.12': optional: true - '@esbuild/darwin-arm64@0.27.0': + '@esbuild/darwin-arm64@0.27.1': optional: true '@esbuild/darwin-x64@0.19.12': @@ -14287,7 +14290,7 @@ snapshots: '@esbuild/darwin-x64@0.25.12': optional: true - '@esbuild/darwin-x64@0.27.0': + '@esbuild/darwin-x64@0.27.1': optional: true '@esbuild/freebsd-arm64@0.19.12': @@ -14296,7 +14299,7 @@ snapshots: '@esbuild/freebsd-arm64@0.25.12': optional: true - '@esbuild/freebsd-arm64@0.27.0': + '@esbuild/freebsd-arm64@0.27.1': optional: true '@esbuild/freebsd-x64@0.19.12': @@ -14305,7 +14308,7 @@ snapshots: '@esbuild/freebsd-x64@0.25.12': optional: true - '@esbuild/freebsd-x64@0.27.0': + '@esbuild/freebsd-x64@0.27.1': optional: true '@esbuild/linux-arm64@0.19.12': @@ -14314,7 +14317,7 @@ snapshots: '@esbuild/linux-arm64@0.25.12': optional: true - '@esbuild/linux-arm64@0.27.0': + '@esbuild/linux-arm64@0.27.1': optional: true '@esbuild/linux-arm@0.19.12': @@ -14323,7 +14326,7 @@ snapshots: '@esbuild/linux-arm@0.25.12': optional: true - '@esbuild/linux-arm@0.27.0': + '@esbuild/linux-arm@0.27.1': optional: true '@esbuild/linux-ia32@0.19.12': @@ -14332,7 +14335,7 @@ snapshots: '@esbuild/linux-ia32@0.25.12': optional: true - '@esbuild/linux-ia32@0.27.0': + '@esbuild/linux-ia32@0.27.1': optional: true '@esbuild/linux-loong64@0.19.12': @@ -14341,7 +14344,7 @@ snapshots: '@esbuild/linux-loong64@0.25.12': optional: true - '@esbuild/linux-loong64@0.27.0': + '@esbuild/linux-loong64@0.27.1': optional: true '@esbuild/linux-mips64el@0.19.12': @@ -14350,7 +14353,7 @@ snapshots: '@esbuild/linux-mips64el@0.25.12': optional: true - '@esbuild/linux-mips64el@0.27.0': + '@esbuild/linux-mips64el@0.27.1': optional: true '@esbuild/linux-ppc64@0.19.12': @@ -14359,7 +14362,7 @@ snapshots: '@esbuild/linux-ppc64@0.25.12': optional: true - '@esbuild/linux-ppc64@0.27.0': + '@esbuild/linux-ppc64@0.27.1': optional: true '@esbuild/linux-riscv64@0.19.12': @@ -14368,7 +14371,7 @@ snapshots: '@esbuild/linux-riscv64@0.25.12': optional: true - '@esbuild/linux-riscv64@0.27.0': + '@esbuild/linux-riscv64@0.27.1': optional: true '@esbuild/linux-s390x@0.19.12': @@ -14377,7 +14380,7 @@ snapshots: '@esbuild/linux-s390x@0.25.12': optional: true - '@esbuild/linux-s390x@0.27.0': + '@esbuild/linux-s390x@0.27.1': optional: true '@esbuild/linux-x64@0.19.12': @@ -14386,13 +14389,13 @@ snapshots: '@esbuild/linux-x64@0.25.12': optional: true - '@esbuild/linux-x64@0.27.0': + '@esbuild/linux-x64@0.27.1': optional: true '@esbuild/netbsd-arm64@0.25.12': optional: true - '@esbuild/netbsd-arm64@0.27.0': + '@esbuild/netbsd-arm64@0.27.1': optional: true '@esbuild/netbsd-x64@0.19.12': @@ -14401,13 +14404,13 @@ snapshots: '@esbuild/netbsd-x64@0.25.12': optional: true - '@esbuild/netbsd-x64@0.27.0': + '@esbuild/netbsd-x64@0.27.1': optional: true '@esbuild/openbsd-arm64@0.25.12': optional: true - '@esbuild/openbsd-arm64@0.27.0': + '@esbuild/openbsd-arm64@0.27.1': optional: true '@esbuild/openbsd-x64@0.19.12': @@ -14416,13 +14419,13 @@ snapshots: '@esbuild/openbsd-x64@0.25.12': optional: true - '@esbuild/openbsd-x64@0.27.0': + '@esbuild/openbsd-x64@0.27.1': optional: true '@esbuild/openharmony-arm64@0.25.12': optional: true - '@esbuild/openharmony-arm64@0.27.0': + '@esbuild/openharmony-arm64@0.27.1': optional: true '@esbuild/sunos-x64@0.19.12': @@ -14431,7 +14434,7 @@ snapshots: '@esbuild/sunos-x64@0.25.12': optional: true - '@esbuild/sunos-x64@0.27.0': + '@esbuild/sunos-x64@0.27.1': optional: true '@esbuild/win32-arm64@0.19.12': @@ -14440,7 +14443,7 @@ snapshots: '@esbuild/win32-arm64@0.25.12': optional: true - '@esbuild/win32-arm64@0.27.0': + '@esbuild/win32-arm64@0.27.1': optional: true '@esbuild/win32-ia32@0.19.12': @@ -14449,7 +14452,7 @@ snapshots: '@esbuild/win32-ia32@0.25.12': optional: true - '@esbuild/win32-ia32@0.27.0': + '@esbuild/win32-ia32@0.27.1': optional: true '@esbuild/win32-x64@0.19.12': @@ -14458,7 +14461,7 @@ snapshots: '@esbuild/win32-x64@0.25.12': optional: true - '@esbuild/win32-x64@0.27.0': + '@esbuild/win32-x64@0.27.1': optional: true '@eslint-community/eslint-utils@4.9.0(eslint@9.39.1(jiti@2.6.1))': @@ -14697,19 +14700,19 @@ snapshots: '@immich/justified-layout-wasm@0.4.3': {} - '@immich/svelte-markdown-preprocess@0.1.0(svelte@5.45.2)': + '@immich/svelte-markdown-preprocess@0.1.0(svelte@5.45.5)': dependencies: - svelte: 5.45.2 + svelte: 5.45.5 - '@immich/ui@0.50.1(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2)': + '@immich/ui@0.50.1(@sveltejs/kit@2.49.1(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.5)(vite@7.2.6(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.5)(vite@7.2.6(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.5)': dependencies: - '@immich/svelte-markdown-preprocess': 0.1.0(svelte@5.45.2) + '@immich/svelte-markdown-preprocess': 0.1.0(svelte@5.45.5) '@internationalized/date': 3.10.0 '@mdi/js': 7.4.47 - bits-ui: 2.14.4(@internationalized/date@3.10.0)(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2) + bits-ui: 2.14.4(@internationalized/date@3.10.0)(@sveltejs/kit@2.49.1(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.5)(vite@7.2.6(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.5)(vite@7.2.6(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.5) luxon: 3.7.2 simple-icons: 15.22.0 - svelte: 5.45.2 + svelte: 5.45.5 svelte-highlight: 7.9.0 tailwind-merge: 3.4.0 tailwind-variants: 3.2.2(tailwind-merge@3.4.0)(tailwindcss@4.1.17) @@ -14719,143 +14722,143 @@ snapshots: '@inquirer/ansi@1.0.2': {} - '@inquirer/checkbox@4.3.2(@types/node@24.10.1)': + '@inquirer/checkbox@4.3.2(@types/node@24.10.2)': dependencies: '@inquirer/ansi': 1.0.2 - '@inquirer/core': 10.3.2(@types/node@24.10.1) + '@inquirer/core': 10.3.2(@types/node@24.10.2) '@inquirer/figures': 1.0.15 - '@inquirer/type': 3.0.10(@types/node@24.10.1) + '@inquirer/type': 3.0.10(@types/node@24.10.2) yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.2 - '@inquirer/confirm@5.1.21(@types/node@24.10.1)': + '@inquirer/confirm@5.1.21(@types/node@24.10.2)': dependencies: - '@inquirer/core': 10.3.2(@types/node@24.10.1) - '@inquirer/type': 3.0.10(@types/node@24.10.1) + '@inquirer/core': 10.3.2(@types/node@24.10.2) + '@inquirer/type': 3.0.10(@types/node@24.10.2) optionalDependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.2 - '@inquirer/core@10.3.2(@types/node@24.10.1)': + '@inquirer/core@10.3.2(@types/node@24.10.2)': dependencies: '@inquirer/ansi': 1.0.2 '@inquirer/figures': 1.0.15 - '@inquirer/type': 3.0.10(@types/node@24.10.1) + '@inquirer/type': 3.0.10(@types/node@24.10.2) cli-width: 4.1.0 mute-stream: 2.0.0 signal-exit: 4.1.0 wrap-ansi: 6.2.0 yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.2 - '@inquirer/editor@4.2.23(@types/node@24.10.1)': + '@inquirer/editor@4.2.23(@types/node@24.10.2)': dependencies: - '@inquirer/core': 10.3.2(@types/node@24.10.1) - '@inquirer/external-editor': 1.0.3(@types/node@24.10.1) - '@inquirer/type': 3.0.10(@types/node@24.10.1) + '@inquirer/core': 10.3.2(@types/node@24.10.2) + '@inquirer/external-editor': 1.0.3(@types/node@24.10.2) + '@inquirer/type': 3.0.10(@types/node@24.10.2) optionalDependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.2 - '@inquirer/expand@4.0.23(@types/node@24.10.1)': + '@inquirer/expand@4.0.23(@types/node@24.10.2)': dependencies: - '@inquirer/core': 10.3.2(@types/node@24.10.1) - '@inquirer/type': 3.0.10(@types/node@24.10.1) + '@inquirer/core': 10.3.2(@types/node@24.10.2) + '@inquirer/type': 3.0.10(@types/node@24.10.2) yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.2 - '@inquirer/external-editor@1.0.3(@types/node@24.10.1)': + '@inquirer/external-editor@1.0.3(@types/node@24.10.2)': dependencies: chardet: 2.1.1 iconv-lite: 0.7.0 optionalDependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.2 '@inquirer/figures@1.0.15': {} - '@inquirer/input@4.3.1(@types/node@24.10.1)': + '@inquirer/input@4.3.1(@types/node@24.10.2)': dependencies: - '@inquirer/core': 10.3.2(@types/node@24.10.1) - '@inquirer/type': 3.0.10(@types/node@24.10.1) + '@inquirer/core': 10.3.2(@types/node@24.10.2) + '@inquirer/type': 3.0.10(@types/node@24.10.2) optionalDependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.2 - '@inquirer/number@3.0.23(@types/node@24.10.1)': + '@inquirer/number@3.0.23(@types/node@24.10.2)': dependencies: - '@inquirer/core': 10.3.2(@types/node@24.10.1) - '@inquirer/type': 3.0.10(@types/node@24.10.1) + '@inquirer/core': 10.3.2(@types/node@24.10.2) + '@inquirer/type': 3.0.10(@types/node@24.10.2) optionalDependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.2 - '@inquirer/password@4.0.23(@types/node@24.10.1)': + '@inquirer/password@4.0.23(@types/node@24.10.2)': dependencies: '@inquirer/ansi': 1.0.2 - '@inquirer/core': 10.3.2(@types/node@24.10.1) - '@inquirer/type': 3.0.10(@types/node@24.10.1) + '@inquirer/core': 10.3.2(@types/node@24.10.2) + '@inquirer/type': 3.0.10(@types/node@24.10.2) optionalDependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.2 - '@inquirer/prompts@7.10.1(@types/node@24.10.1)': + '@inquirer/prompts@7.10.1(@types/node@24.10.2)': dependencies: - '@inquirer/checkbox': 4.3.2(@types/node@24.10.1) - '@inquirer/confirm': 5.1.21(@types/node@24.10.1) - '@inquirer/editor': 4.2.23(@types/node@24.10.1) - '@inquirer/expand': 4.0.23(@types/node@24.10.1) - '@inquirer/input': 4.3.1(@types/node@24.10.1) - '@inquirer/number': 3.0.23(@types/node@24.10.1) - '@inquirer/password': 4.0.23(@types/node@24.10.1) - '@inquirer/rawlist': 4.1.11(@types/node@24.10.1) - '@inquirer/search': 3.2.2(@types/node@24.10.1) - '@inquirer/select': 4.4.2(@types/node@24.10.1) + '@inquirer/checkbox': 4.3.2(@types/node@24.10.2) + '@inquirer/confirm': 5.1.21(@types/node@24.10.2) + '@inquirer/editor': 4.2.23(@types/node@24.10.2) + '@inquirer/expand': 4.0.23(@types/node@24.10.2) + '@inquirer/input': 4.3.1(@types/node@24.10.2) + '@inquirer/number': 3.0.23(@types/node@24.10.2) + '@inquirer/password': 4.0.23(@types/node@24.10.2) + '@inquirer/rawlist': 4.1.11(@types/node@24.10.2) + '@inquirer/search': 3.2.2(@types/node@24.10.2) + '@inquirer/select': 4.4.2(@types/node@24.10.2) optionalDependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.2 - '@inquirer/prompts@7.3.2(@types/node@24.10.1)': + '@inquirer/prompts@7.3.2(@types/node@24.10.2)': dependencies: - '@inquirer/checkbox': 4.3.2(@types/node@24.10.1) - '@inquirer/confirm': 5.1.21(@types/node@24.10.1) - '@inquirer/editor': 4.2.23(@types/node@24.10.1) - '@inquirer/expand': 4.0.23(@types/node@24.10.1) - '@inquirer/input': 4.3.1(@types/node@24.10.1) - '@inquirer/number': 3.0.23(@types/node@24.10.1) - '@inquirer/password': 4.0.23(@types/node@24.10.1) - '@inquirer/rawlist': 4.1.11(@types/node@24.10.1) - '@inquirer/search': 3.2.2(@types/node@24.10.1) - '@inquirer/select': 4.4.2(@types/node@24.10.1) + '@inquirer/checkbox': 4.3.2(@types/node@24.10.2) + '@inquirer/confirm': 5.1.21(@types/node@24.10.2) + '@inquirer/editor': 4.2.23(@types/node@24.10.2) + '@inquirer/expand': 4.0.23(@types/node@24.10.2) + '@inquirer/input': 4.3.1(@types/node@24.10.2) + '@inquirer/number': 3.0.23(@types/node@24.10.2) + '@inquirer/password': 4.0.23(@types/node@24.10.2) + '@inquirer/rawlist': 4.1.11(@types/node@24.10.2) + '@inquirer/search': 3.2.2(@types/node@24.10.2) + '@inquirer/select': 4.4.2(@types/node@24.10.2) optionalDependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.2 - '@inquirer/rawlist@4.1.11(@types/node@24.10.1)': + '@inquirer/rawlist@4.1.11(@types/node@24.10.2)': dependencies: - '@inquirer/core': 10.3.2(@types/node@24.10.1) - '@inquirer/type': 3.0.10(@types/node@24.10.1) + '@inquirer/core': 10.3.2(@types/node@24.10.2) + '@inquirer/type': 3.0.10(@types/node@24.10.2) yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.2 - '@inquirer/search@3.2.2(@types/node@24.10.1)': + '@inquirer/search@3.2.2(@types/node@24.10.2)': dependencies: - '@inquirer/core': 10.3.2(@types/node@24.10.1) + '@inquirer/core': 10.3.2(@types/node@24.10.2) '@inquirer/figures': 1.0.15 - '@inquirer/type': 3.0.10(@types/node@24.10.1) + '@inquirer/type': 3.0.10(@types/node@24.10.2) yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.2 - '@inquirer/select@4.4.2(@types/node@24.10.1)': + '@inquirer/select@4.4.2(@types/node@24.10.2)': dependencies: '@inquirer/ansi': 1.0.2 - '@inquirer/core': 10.3.2(@types/node@24.10.1) + '@inquirer/core': 10.3.2(@types/node@24.10.2) '@inquirer/figures': 1.0.15 - '@inquirer/type': 3.0.10(@types/node@24.10.1) + '@inquirer/type': 3.0.10(@types/node@24.10.2) yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.2 - '@inquirer/type@3.0.10(@types/node@24.10.1)': + '@inquirer/type@3.0.10(@types/node@24.10.2)': optionalDependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.2 '@internationalized/date@3.10.0': dependencies: @@ -14893,7 +14896,7 @@ snapshots: '@jest/schemas': 29.6.3 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 24.10.1 + '@types/node': 24.10.2 '@types/yargs': 17.0.34 chalk: 4.1.2 @@ -14975,8 +14978,8 @@ snapshots: '@koddsson/eslint-plugin-tscompat@0.2.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)': dependencies: '@mdn/browser-compat-data': 6.0.27 - '@typescript-eslint/type-utils': 8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/utils': 8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/type-utils': 8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/utils': 8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) browserslist: 4.28.0 transitivePeerDependencies: - eslint @@ -15069,11 +15072,11 @@ snapshots: rw: 1.3.3 tinyqueue: 3.0.0 - '@maplibre/mlt@1.1.0': + '@maplibre/mlt@1.1.2': dependencies: '@mapbox/point-geometry': 1.1.0 - '@maplibre/vt-pbf@4.0.3': + '@maplibre/vt-pbf@4.1.0': dependencies: '@mapbox/point-geometry': 1.1.0 '@mapbox/vector-tile': 2.0.4 @@ -15157,20 +15160,20 @@ snapshots: '@nestjs/core': 11.1.9(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.9)(@nestjs/websockets@11.1.9)(reflect-metadata@0.2.2)(rxjs@7.8.2) tslib: 2.8.1 - '@nestjs/bullmq@11.0.4(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(bullmq@5.65.0)': + '@nestjs/bullmq@11.0.4(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(bullmq@5.65.1)': dependencies: '@nestjs/bull-shared': 11.0.4(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9) '@nestjs/common': 11.1.9(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2) '@nestjs/core': 11.1.9(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.9)(@nestjs/websockets@11.1.9)(reflect-metadata@0.2.2)(rxjs@7.8.2) - bullmq: 5.65.0 + bullmq: 5.65.1 tslib: 2.8.1 - '@nestjs/cli@11.0.13(@swc/core@1.15.3(@swc/helpers@0.5.17))(@types/node@24.10.1)': + '@nestjs/cli@11.0.14(@swc/core@1.15.3(@swc/helpers@0.5.17))(@types/node@24.10.2)': dependencies: '@angular-devkit/core': 19.2.19(chokidar@4.0.3) '@angular-devkit/schematics': 19.2.19(chokidar@4.0.3) - '@angular-devkit/schematics-cli': 19.2.19(@types/node@24.10.1)(chokidar@4.0.3) - '@inquirer/prompts': 7.10.1(@types/node@24.10.1) + '@angular-devkit/schematics-cli': 19.2.19(@types/node@24.10.2)(chokidar@4.0.3) + '@inquirer/prompts': 7.10.1(@types/node@24.10.2) '@nestjs/schematics': 11.0.9(chokidar@4.0.3)(typescript@5.9.3) ansis: 4.2.0 chokidar: 4.0.3 @@ -15698,117 +15701,117 @@ snapshots: '@protobufjs/utf8@1.1.0': {} - '@react-email/body@0.1.0(react@19.2.0)': + '@react-email/body@0.1.0(react@19.2.1)': dependencies: - react: 19.2.0 + react: 19.2.1 - '@react-email/button@0.2.0(react@19.2.0)': + '@react-email/button@0.2.0(react@19.2.1)': dependencies: - react: 19.2.0 + react: 19.2.1 - '@react-email/code-block@0.1.0(react@19.2.0)': + '@react-email/code-block@0.1.0(react@19.2.1)': dependencies: prismjs: 1.30.0 - react: 19.2.0 + react: 19.2.1 - '@react-email/code-inline@0.0.5(react@19.2.0)': + '@react-email/code-inline@0.0.5(react@19.2.1)': dependencies: - react: 19.2.0 + react: 19.2.1 - '@react-email/column@0.0.13(react@19.2.0)': + '@react-email/column@0.0.13(react@19.2.1)': dependencies: - react: 19.2.0 + react: 19.2.1 - '@react-email/components@0.5.7(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + '@react-email/components@0.5.7(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': dependencies: - '@react-email/body': 0.1.0(react@19.2.0) - '@react-email/button': 0.2.0(react@19.2.0) - '@react-email/code-block': 0.1.0(react@19.2.0) - '@react-email/code-inline': 0.0.5(react@19.2.0) - '@react-email/column': 0.0.13(react@19.2.0) - '@react-email/container': 0.0.15(react@19.2.0) - '@react-email/font': 0.0.9(react@19.2.0) - '@react-email/head': 0.0.12(react@19.2.0) - '@react-email/heading': 0.0.15(react@19.2.0) - '@react-email/hr': 0.0.11(react@19.2.0) - '@react-email/html': 0.0.11(react@19.2.0) - '@react-email/img': 0.0.11(react@19.2.0) - '@react-email/link': 0.0.12(react@19.2.0) - '@react-email/markdown': 0.0.16(react@19.2.0) - '@react-email/preview': 0.0.13(react@19.2.0) - '@react-email/render': 1.4.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-email/row': 0.0.12(react@19.2.0) - '@react-email/section': 0.0.16(react@19.2.0) - '@react-email/tailwind': 1.2.2(react@19.2.0) - '@react-email/text': 0.1.5(react@19.2.0) - react: 19.2.0 + '@react-email/body': 0.1.0(react@19.2.1) + '@react-email/button': 0.2.0(react@19.2.1) + '@react-email/code-block': 0.1.0(react@19.2.1) + '@react-email/code-inline': 0.0.5(react@19.2.1) + '@react-email/column': 0.0.13(react@19.2.1) + '@react-email/container': 0.0.15(react@19.2.1) + '@react-email/font': 0.0.9(react@19.2.1) + '@react-email/head': 0.0.12(react@19.2.1) + '@react-email/heading': 0.0.15(react@19.2.1) + '@react-email/hr': 0.0.11(react@19.2.1) + '@react-email/html': 0.0.11(react@19.2.1) + '@react-email/img': 0.0.11(react@19.2.1) + '@react-email/link': 0.0.12(react@19.2.1) + '@react-email/markdown': 0.0.16(react@19.2.1) + '@react-email/preview': 0.0.13(react@19.2.1) + '@react-email/render': 1.4.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@react-email/row': 0.0.12(react@19.2.1) + '@react-email/section': 0.0.16(react@19.2.1) + '@react-email/tailwind': 1.2.2(react@19.2.1) + '@react-email/text': 0.1.5(react@19.2.1) + react: 19.2.1 transitivePeerDependencies: - react-dom - '@react-email/container@0.0.15(react@19.2.0)': + '@react-email/container@0.0.15(react@19.2.1)': dependencies: - react: 19.2.0 + react: 19.2.1 - '@react-email/font@0.0.9(react@19.2.0)': + '@react-email/font@0.0.9(react@19.2.1)': dependencies: - react: 19.2.0 + react: 19.2.1 - '@react-email/head@0.0.12(react@19.2.0)': + '@react-email/head@0.0.12(react@19.2.1)': dependencies: - react: 19.2.0 + react: 19.2.1 - '@react-email/heading@0.0.15(react@19.2.0)': + '@react-email/heading@0.0.15(react@19.2.1)': dependencies: - react: 19.2.0 + react: 19.2.1 - '@react-email/hr@0.0.11(react@19.2.0)': + '@react-email/hr@0.0.11(react@19.2.1)': dependencies: - react: 19.2.0 + react: 19.2.1 - '@react-email/html@0.0.11(react@19.2.0)': + '@react-email/html@0.0.11(react@19.2.1)': dependencies: - react: 19.2.0 + react: 19.2.1 - '@react-email/img@0.0.11(react@19.2.0)': + '@react-email/img@0.0.11(react@19.2.1)': dependencies: - react: 19.2.0 + react: 19.2.1 - '@react-email/link@0.0.12(react@19.2.0)': + '@react-email/link@0.0.12(react@19.2.1)': dependencies: - react: 19.2.0 + react: 19.2.1 - '@react-email/markdown@0.0.16(react@19.2.0)': + '@react-email/markdown@0.0.16(react@19.2.1)': dependencies: marked: 15.0.12 - react: 19.2.0 + react: 19.2.1 - '@react-email/preview@0.0.13(react@19.2.0)': + '@react-email/preview@0.0.13(react@19.2.1)': dependencies: - react: 19.2.0 + react: 19.2.1 - '@react-email/render@1.4.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + '@react-email/render@1.4.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': dependencies: html-to-text: 9.0.5 prettier: 3.7.4 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) react-promise-suspense: 0.3.4 - '@react-email/row@0.0.12(react@19.2.0)': + '@react-email/row@0.0.12(react@19.2.1)': dependencies: - react: 19.2.0 + react: 19.2.1 - '@react-email/section@0.0.16(react@19.2.0)': + '@react-email/section@0.0.16(react@19.2.1)': dependencies: - react: 19.2.0 + react: 19.2.1 - '@react-email/tailwind@1.2.2(react@19.2.0)': + '@react-email/tailwind@1.2.2(react@19.2.1)': dependencies: - react: 19.2.0 + react: 19.2.1 - '@react-email/text@0.1.5(react@19.2.0)': + '@react-email/text@0.1.5(react@19.2.1)': dependencies: - react: 19.2.0 + react: 19.2.1 '@rollup/pluginutils@5.3.0(rollup@4.53.3)': dependencies: @@ -16212,33 +16215,33 @@ snapshots: dependencies: acorn: 8.15.0 - '@sveltejs/adapter-static@3.0.10(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))': + '@sveltejs/adapter-static@3.0.10(@sveltejs/kit@2.49.1(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.5)(vite@7.2.6(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.5)(vite@7.2.6(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))': dependencies: - '@sveltejs/kit': 2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)) + '@sveltejs/kit': 2.49.1(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.5)(vite@7.2.6(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.5)(vite@7.2.6(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)) - '@sveltejs/enhanced-img@0.9.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(rollup@4.53.3)(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2))': + '@sveltejs/enhanced-img@0.9.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.5)(vite@7.2.6(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(rollup@4.53.3)(svelte@5.45.5)(vite@7.2.6(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2))': dependencies: - '@sveltejs/vite-plugin-svelte': 6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)) + '@sveltejs/vite-plugin-svelte': 6.2.1(svelte@5.45.5)(vite@7.2.6(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)) magic-string: 0.30.21 sharp: 0.34.5 - svelte: 5.45.2 - svelte-parse-markup: 0.1.5(svelte@5.45.2) - vite: 7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2) + svelte: 5.45.5 + svelte-parse-markup: 0.1.5(svelte@5.45.5) + vite: 7.2.6(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2) vite-imagetools: 9.0.2(rollup@4.53.3) zimmerframe: 1.1.4 transitivePeerDependencies: - rollup - supports-color - '@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2))': + '@sveltejs/kit@2.49.1(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.5)(vite@7.2.6(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.5)(vite@7.2.6(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2))': dependencies: '@standard-schema/spec': 1.0.0 '@sveltejs/acorn-typescript': 1.0.8(acorn@8.15.0) - '@sveltejs/vite-plugin-svelte': 6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)) + '@sveltejs/vite-plugin-svelte': 6.2.1(svelte@5.45.5)(vite@7.2.6(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)) '@types/cookie': 0.6.0 acorn: 8.15.0 cookie: 0.6.0 - devalue: 5.5.0 + devalue: 5.6.0 esm-env: 1.2.2 kleur: 4.1.5 magic-string: 0.30.21 @@ -16246,29 +16249,29 @@ snapshots: sade: 1.8.1 set-cookie-parser: 2.7.2 sirv: 3.0.2 - svelte: 5.45.2 - vite: 7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2) + svelte: 5.45.5 + vite: 7.2.6(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2) optionalDependencies: '@opentelemetry/api': 1.9.0 - '@sveltejs/vite-plugin-svelte-inspector@5.0.0(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2))': + '@sveltejs/vite-plugin-svelte-inspector@5.0.0(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.5)(vite@7.2.6(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.5)(vite@7.2.6(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2))': dependencies: - '@sveltejs/vite-plugin-svelte': 6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)) + '@sveltejs/vite-plugin-svelte': 6.2.1(svelte@5.45.5)(vite@7.2.6(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)) debug: 4.4.3 - svelte: 5.45.2 - vite: 7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2) + svelte: 5.45.5 + vite: 7.2.6(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2) transitivePeerDependencies: - supports-color - '@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2))': + '@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.5)(vite@7.2.6(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2))': dependencies: - '@sveltejs/vite-plugin-svelte-inspector': 5.0.0(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)) + '@sveltejs/vite-plugin-svelte-inspector': 5.0.0(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.5)(vite@7.2.6(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.5)(vite@7.2.6(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)) debug: 4.4.3 deepmerge: 4.3.1 magic-string: 0.30.21 - svelte: 5.45.2 - vite: 7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2) - vitefu: 1.1.1(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)) + svelte: 5.45.5 + vite: 7.2.6(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2) + vitefu: 1.1.1(vite@7.2.6(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)) transitivePeerDependencies: - supports-color @@ -16487,12 +16490,12 @@ snapshots: '@tailwindcss/oxide-win32-arm64-msvc': 4.1.17 '@tailwindcss/oxide-win32-x64-msvc': 4.1.17 - '@tailwindcss/vite@4.1.17(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2))': + '@tailwindcss/vite@4.1.17(vite@7.2.6(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2))': dependencies: '@tailwindcss/node': 4.1.17 '@tailwindcss/oxide': 4.1.17 tailwindcss: 4.1.17 - vite: 7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2) + vite: 7.2.6(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2) '@testing-library/dom@10.4.1': dependencies: @@ -16514,13 +16517,13 @@ snapshots: picocolors: 1.1.1 redent: 3.0.0 - '@testing-library/svelte@5.2.9(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2))(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.10.1)(happy-dom@20.0.10)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2))': + '@testing-library/svelte@5.2.9(svelte@5.45.5)(vite@7.2.6(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2))(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.10.2)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2))': dependencies: '@testing-library/dom': 10.4.1 - svelte: 5.45.2 + svelte: 5.45.5 optionalDependencies: - vite: 7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2) - vitest: 3.2.4(@types/debug@4.1.12)(@types/node@24.10.1)(happy-dom@20.0.10)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2) + vite: 7.2.6(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2) + vitest: 3.2.4(@types/debug@4.1.12)(@types/node@24.10.2)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2) '@testing-library/user-event@14.6.1(@testing-library/dom@10.4.1)': dependencies: @@ -16562,7 +16565,7 @@ snapshots: '@types/accepts@1.3.7': dependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.2 '@types/archiver@7.0.0': dependencies: @@ -16574,16 +16577,16 @@ snapshots: '@types/bcrypt@6.0.0': dependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.2 '@types/body-parser@1.19.6': dependencies: '@types/connect': 3.4.38 - '@types/node': 24.10.1 + '@types/node': 24.10.2 '@types/bonjour@3.5.13': dependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.2 '@types/braces@3.0.5': {} @@ -16604,27 +16607,27 @@ snapshots: '@types/cli-progress@3.11.6': dependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.2 '@types/compression@1.8.1': dependencies: - '@types/express': 5.0.5 - '@types/node': 24.10.1 + '@types/express': 5.0.6 + '@types/node': 24.10.2 '@types/connect-history-api-fallback@1.5.4': dependencies: '@types/express-serve-static-core': 5.1.0 - '@types/node': 24.10.1 + '@types/node': 24.10.2 '@types/connect@3.4.38': dependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.2 '@types/content-disposition@0.5.9': {} - '@types/cookie-parser@1.4.10(@types/express@5.0.5)': + '@types/cookie-parser@1.4.10(@types/express@5.0.6)': dependencies: - '@types/express': 5.0.5 + '@types/express': 5.0.6 '@types/cookie@0.6.0': {} @@ -16633,13 +16636,13 @@ snapshots: '@types/cookies@0.9.1': dependencies: '@types/connect': 3.4.38 - '@types/express': 5.0.5 + '@types/express': 5.0.6 '@types/keygrip': 1.0.6 - '@types/node': 24.10.1 + '@types/node': 24.10.2 '@types/cors@2.8.19': dependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.2 '@types/debug@4.1.12': dependencies: @@ -16649,13 +16652,13 @@ snapshots: '@types/docker-modem@3.0.6': dependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.2 '@types/ssh2': 1.15.5 '@types/dockerode@3.3.47': dependencies: '@types/docker-modem': 3.0.6 - '@types/node': 24.10.1 + '@types/node': 24.10.2 '@types/ssh2': 1.15.5 '@types/dom-to-image@2.6.7': {} @@ -16678,14 +16681,14 @@ snapshots: '@types/express-serve-static-core@4.19.7': dependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.2 '@types/qs': 6.14.0 '@types/range-parser': 1.2.7 '@types/send': 1.2.1 '@types/express-serve-static-core@5.1.0': dependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.2 '@types/qs': 6.14.0 '@types/range-parser': 1.2.7 '@types/send': 1.2.1 @@ -16697,11 +16700,11 @@ snapshots: '@types/qs': 6.14.0 '@types/serve-static': 1.15.10 - '@types/express@5.0.5': + '@types/express@5.0.6': dependencies: '@types/body-parser': 1.19.6 '@types/express-serve-static-core': 5.1.0 - '@types/serve-static': 1.15.10 + '@types/serve-static': 2.2.0 '@types/filesystem@0.0.36': dependencies: @@ -16711,7 +16714,7 @@ snapshots: '@types/fluent-ffmpeg@2.1.28': dependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.2 '@types/geojson-vt@3.2.5': dependencies: @@ -16743,7 +16746,7 @@ snapshots: '@types/http-proxy@1.17.17': dependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.2 '@types/inquirer@8.2.11': dependencies: @@ -16767,7 +16770,7 @@ snapshots: '@types/jsonwebtoken@9.0.10': dependencies: '@types/ms': 2.1.0 - '@types/node': 24.10.1 + '@types/node': 24.10.2 '@types/justified-layout@4.1.4': {} @@ -16786,7 +16789,7 @@ snapshots: '@types/http-errors': 2.0.5 '@types/keygrip': 1.0.6 '@types/koa-compose': 3.2.8 - '@types/node': 24.10.1 + '@types/node': 24.10.2 '@types/leaflet@1.9.21': dependencies: @@ -16816,17 +16819,17 @@ snapshots: '@types/mock-fs@4.13.4': dependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.2 '@types/ms@2.1.0': {} '@types/multer@2.0.0': dependencies: - '@types/express': 5.0.5 + '@types/express': 5.0.6 '@types/node-forge@1.3.14': dependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.2 '@types/node@17.0.45': {} @@ -16834,18 +16837,18 @@ snapshots: dependencies: undici-types: 5.26.5 - '@types/node@20.19.24': + '@types/node@20.19.26': dependencies: undici-types: 6.21.0 - '@types/node@24.10.1': + '@types/node@24.10.2': dependencies: undici-types: 7.16.0 '@types/nodemailer@7.0.4': dependencies: '@aws-sdk/client-sesv2': 3.939.0 - '@types/node': 24.10.1 + '@types/node': 24.10.2 transitivePeerDependencies: - aws-crt @@ -16853,7 +16856,7 @@ snapshots: dependencies: '@types/keygrip': 1.0.6 '@types/koa': 3.0.0 - '@types/node': 24.10.1 + '@types/node': 24.10.2 '@types/parse5@5.0.3': {} @@ -16863,7 +16866,7 @@ snapshots: '@types/pg@8.15.6': dependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.2 pg-protocol: 1.10.3 pg-types: 2.2.0 @@ -16871,13 +16874,13 @@ snapshots: '@types/pngjs@6.0.5': dependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.2 '@types/prismjs@1.26.5': {} '@types/qrcode@1.5.6': dependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.2 '@types/qs@6.14.0': {} @@ -16906,7 +16909,7 @@ snapshots: '@types/readdir-glob@1.1.5': dependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.2 '@types/retry@0.12.2': {} @@ -16916,40 +16919,45 @@ snapshots: '@types/sax@1.2.7': dependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.2 '@types/semver@7.7.1': {} '@types/send@0.17.6': dependencies: '@types/mime': 1.3.5 - '@types/node': 24.10.1 + '@types/node': 24.10.2 '@types/send@1.2.1': dependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.2 '@types/serve-index@1.9.4': dependencies: - '@types/express': 5.0.5 + '@types/express': 5.0.6 '@types/serve-static@1.15.10': dependencies: '@types/http-errors': 2.0.5 - '@types/node': 24.10.1 + '@types/node': 24.10.2 '@types/send': 0.17.6 + '@types/serve-static@2.2.0': + dependencies: + '@types/http-errors': 2.0.5 + '@types/node': 24.10.2 + '@types/sockjs@0.3.36': dependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.2 '@types/ssh2-streams@0.1.13': dependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.2 '@types/ssh2@0.5.52': dependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.2 '@types/ssh2-streams': 0.1.13 '@types/ssh2@1.15.5': @@ -16960,7 +16968,7 @@ snapshots: dependencies: '@types/cookiejar': 2.1.5 '@types/methods': 1.1.4 - '@types/node': 24.10.1 + '@types/node': 24.10.2 form-data: 4.0.5 '@types/supercluster@7.1.3': @@ -16974,7 +16982,7 @@ snapshots: '@types/through@0.0.33': dependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.2 '@types/ua-parser-js@0.7.39': {} @@ -16988,7 +16996,7 @@ snapshots: '@types/ws@8.18.1': dependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.2 '@types/yargs-parser@21.0.3': {} @@ -16996,14 +17004,14 @@ snapshots: dependencies: '@types/yargs-parser': 21.0.3 - '@typescript-eslint/eslint-plugin@8.48.0(@typescript-eslint/parser@8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/eslint-plugin@8.48.1(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.48.0 - '@typescript-eslint/type-utils': 8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/utils': 8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.48.0 + '@typescript-eslint/parser': 8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.48.1 + '@typescript-eslint/type-utils': 8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/utils': 8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.48.1 eslint: 9.39.1(jiti@2.6.1) graphemer: 1.4.0 ignore: 7.0.5 @@ -17013,41 +17021,41 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)': dependencies: - '@typescript-eslint/scope-manager': 8.48.0 - '@typescript-eslint/types': 8.48.0 - '@typescript-eslint/typescript-estree': 8.48.0(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.48.0 + '@typescript-eslint/scope-manager': 8.48.1 + '@typescript-eslint/types': 8.48.1 + '@typescript-eslint/typescript-estree': 8.48.1(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.48.1 debug: 4.4.3 eslint: 9.39.1(jiti@2.6.1) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.48.0(typescript@5.9.3)': + '@typescript-eslint/project-service@8.48.1(typescript@5.9.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.48.0(typescript@5.9.3) - '@typescript-eslint/types': 8.48.0 + '@typescript-eslint/tsconfig-utils': 8.48.1(typescript@5.9.3) + '@typescript-eslint/types': 8.48.1 debug: 4.4.3 typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.48.0': + '@typescript-eslint/scope-manager@8.48.1': dependencies: - '@typescript-eslint/types': 8.48.0 - '@typescript-eslint/visitor-keys': 8.48.0 + '@typescript-eslint/types': 8.48.1 + '@typescript-eslint/visitor-keys': 8.48.1 - '@typescript-eslint/tsconfig-utils@8.48.0(typescript@5.9.3)': + '@typescript-eslint/tsconfig-utils@8.48.1(typescript@5.9.3)': dependencies: typescript: 5.9.3 - '@typescript-eslint/type-utils@8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/type-utils@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)': dependencies: - '@typescript-eslint/types': 8.48.0 - '@typescript-eslint/typescript-estree': 8.48.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/types': 8.48.1 + '@typescript-eslint/typescript-estree': 8.48.1(typescript@5.9.3) + '@typescript-eslint/utils': 8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) debug: 4.4.3 eslint: 9.39.1(jiti@2.6.1) ts-api-utils: 2.1.0(typescript@5.9.3) @@ -17055,14 +17063,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.48.0': {} + '@typescript-eslint/types@8.48.1': {} - '@typescript-eslint/typescript-estree@8.48.0(typescript@5.9.3)': + '@typescript-eslint/typescript-estree@8.48.1(typescript@5.9.3)': dependencies: - '@typescript-eslint/project-service': 8.48.0(typescript@5.9.3) - '@typescript-eslint/tsconfig-utils': 8.48.0(typescript@5.9.3) - '@typescript-eslint/types': 8.48.0 - '@typescript-eslint/visitor-keys': 8.48.0 + '@typescript-eslint/project-service': 8.48.1(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.48.1(typescript@5.9.3) + '@typescript-eslint/types': 8.48.1 + '@typescript-eslint/visitor-keys': 8.48.1 debug: 4.4.3 minimatch: 9.0.5 semver: 7.7.3 @@ -17072,27 +17080,27 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/utils@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)': dependencies: '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@2.6.1)) - '@typescript-eslint/scope-manager': 8.48.0 - '@typescript-eslint/types': 8.48.0 - '@typescript-eslint/typescript-estree': 8.48.0(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.48.1 + '@typescript-eslint/types': 8.48.1 + '@typescript-eslint/typescript-estree': 8.48.1(typescript@5.9.3) eslint: 9.39.1(jiti@2.6.1) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.48.0': + '@typescript-eslint/visitor-keys@8.48.1': dependencies: - '@typescript-eslint/types': 8.48.0 + '@typescript-eslint/types': 8.48.1 eslint-visitor-keys: 4.2.1 '@ungap/structured-clone@1.3.0': {} '@vercel/oidc@3.0.3': {} - '@vitest/coverage-v8@3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.10.1)(happy-dom@20.0.10)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2))': + '@vitest/coverage-v8@3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.10.2)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2))': dependencies: '@ampproject/remapping': 2.3.0 '@bcoe/v8-coverage': 1.0.2 @@ -17107,7 +17115,7 @@ snapshots: std-env: 3.10.0 test-exclude: 7.0.1 tinyrainbow: 2.0.0 - vitest: 3.2.4(@types/debug@4.1.12)(@types/node@24.10.1)(happy-dom@20.0.10)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2) + vitest: 3.2.4(@types/debug@4.1.12)(@types/node@24.10.2)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2) transitivePeerDependencies: - supports-color @@ -17119,13 +17127,13 @@ snapshots: chai: 5.2.0 tinyrainbow: 2.0.0 - '@vitest/mocker@3.2.4(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2))': + '@vitest/mocker@3.2.4(vite@7.2.6(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2))': dependencies: '@vitest/spy': 3.2.4 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2) + vite: 7.2.6(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2) '@vitest/pretty-format@3.2.4': dependencies: @@ -17237,10 +17245,10 @@ snapshots: dependencies: '@namnode/store': 0.1.0 - '@zoom-image/svelte@0.3.8(svelte@5.45.2)': + '@zoom-image/svelte@0.3.8(svelte@5.45.5)': dependencies: '@zoom-image/core': 0.41.4 - svelte: 5.45.2 + svelte: 5.45.5 abab@2.0.6: optional: true @@ -17603,15 +17611,15 @@ snapshots: binary-extensions@2.3.0: {} - bits-ui@2.14.4(@internationalized/date@3.10.0)(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2): + bits-ui@2.14.4(@internationalized/date@3.10.0)(@sveltejs/kit@2.49.1(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.5)(vite@7.2.6(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.5)(vite@7.2.6(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.5): dependencies: '@floating-ui/core': 1.7.3 '@floating-ui/dom': 1.7.4 '@internationalized/date': 3.10.0 esm-env: 1.2.2 - runed: 0.35.1(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2) - svelte: 5.45.2 - svelte-toolbelt: 0.10.6(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2) + runed: 0.35.1(@sveltejs/kit@2.49.1(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.5)(vite@7.2.6(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.5)(vite@7.2.6(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.5) + svelte: 5.45.5 + svelte-toolbelt: 0.10.6(@sveltejs/kit@2.49.1(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.5)(vite@7.2.6(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.5)(vite@7.2.6(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.5) tabbable: 6.3.0 transitivePeerDependencies: - '@sveltejs/kit' @@ -17726,7 +17734,7 @@ snapshots: builtin-modules@5.0.0: {} - bullmq@5.65.0: + bullmq@5.65.1: dependencies: cron-parser: 4.9.0 ioredis: 5.8.2 @@ -18216,7 +18224,7 @@ snapshots: css-blank-pseudo@7.0.1(postcss@8.5.6): dependencies: postcss: 8.5.6 - postcss-selector-parser: 7.1.0 + postcss-selector-parser: 7.1.1 css-declaration-sorter@7.3.0(postcss@8.5.6): dependencies: @@ -18224,9 +18232,9 @@ snapshots: css-has-pseudo@7.0.3(postcss@8.5.6): dependencies: - '@csstools/selector-specificity': 5.0.0(postcss-selector-parser@7.1.0) + '@csstools/selector-specificity': 5.0.0(postcss-selector-parser@7.1.1) postcss: 8.5.6 - postcss-selector-parser: 7.1.0 + postcss-selector-parser: 7.1.1 postcss-value-parser: 4.2.0 css-loader@6.11.0(webpack@5.103.0): @@ -18499,7 +18507,7 @@ snapshots: transitivePeerDependencies: - supports-color - devalue@5.5.0: {} + devalue@5.6.0: {} devlop@1.1.0: dependencies: @@ -18699,7 +18707,7 @@ snapshots: engine.io@6.6.4: dependencies: '@types/cors': 2.8.19 - '@types/node': 24.10.1 + '@types/node': 24.10.2 accepts: 1.3.8 base64id: 2.0.0 cookie: 0.7.2 @@ -18842,34 +18850,34 @@ snapshots: '@esbuild/win32-ia32': 0.25.12 '@esbuild/win32-x64': 0.25.12 - esbuild@0.27.0: + esbuild@0.27.1: optionalDependencies: - '@esbuild/aix-ppc64': 0.27.0 - '@esbuild/android-arm': 0.27.0 - '@esbuild/android-arm64': 0.27.0 - '@esbuild/android-x64': 0.27.0 - '@esbuild/darwin-arm64': 0.27.0 - '@esbuild/darwin-x64': 0.27.0 - '@esbuild/freebsd-arm64': 0.27.0 - '@esbuild/freebsd-x64': 0.27.0 - '@esbuild/linux-arm': 0.27.0 - '@esbuild/linux-arm64': 0.27.0 - '@esbuild/linux-ia32': 0.27.0 - '@esbuild/linux-loong64': 0.27.0 - '@esbuild/linux-mips64el': 0.27.0 - '@esbuild/linux-ppc64': 0.27.0 - '@esbuild/linux-riscv64': 0.27.0 - '@esbuild/linux-s390x': 0.27.0 - '@esbuild/linux-x64': 0.27.0 - '@esbuild/netbsd-arm64': 0.27.0 - '@esbuild/netbsd-x64': 0.27.0 - '@esbuild/openbsd-arm64': 0.27.0 - '@esbuild/openbsd-x64': 0.27.0 - '@esbuild/openharmony-arm64': 0.27.0 - '@esbuild/sunos-x64': 0.27.0 - '@esbuild/win32-arm64': 0.27.0 - '@esbuild/win32-ia32': 0.27.0 - '@esbuild/win32-x64': 0.27.0 + '@esbuild/aix-ppc64': 0.27.1 + '@esbuild/android-arm': 0.27.1 + '@esbuild/android-arm64': 0.27.1 + '@esbuild/android-x64': 0.27.1 + '@esbuild/darwin-arm64': 0.27.1 + '@esbuild/darwin-x64': 0.27.1 + '@esbuild/freebsd-arm64': 0.27.1 + '@esbuild/freebsd-x64': 0.27.1 + '@esbuild/linux-arm': 0.27.1 + '@esbuild/linux-arm64': 0.27.1 + '@esbuild/linux-ia32': 0.27.1 + '@esbuild/linux-loong64': 0.27.1 + '@esbuild/linux-mips64el': 0.27.1 + '@esbuild/linux-ppc64': 0.27.1 + '@esbuild/linux-riscv64': 0.27.1 + '@esbuild/linux-s390x': 0.27.1 + '@esbuild/linux-x64': 0.27.1 + '@esbuild/netbsd-arm64': 0.27.1 + '@esbuild/netbsd-x64': 0.27.1 + '@esbuild/openbsd-arm64': 0.27.1 + '@esbuild/openbsd-x64': 0.27.1 + '@esbuild/openharmony-arm64': 0.27.1 + '@esbuild/sunos-x64': 0.27.1 + '@esbuild/win32-arm64': 0.27.1 + '@esbuild/win32-ia32': 0.27.1 + '@esbuild/win32-x64': 0.27.1 escalade@3.2.0: {} @@ -18918,7 +18926,7 @@ snapshots: '@types/eslint': 9.6.1 eslint-config-prettier: 10.1.8(eslint@9.39.1(jiti@2.6.1)) - eslint-plugin-svelte@3.13.0(eslint@9.39.1(jiti@2.6.1))(svelte@5.45.2): + eslint-plugin-svelte@3.13.1(eslint@9.39.1(jiti@2.6.1))(svelte@5.45.5): dependencies: '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@2.6.1)) '@jridgewell/sourcemap-codec': 1.5.5 @@ -18930,9 +18938,9 @@ snapshots: postcss-load-config: 3.1.4(postcss@8.5.6) postcss-safe-parser: 7.0.1(postcss@8.5.6) semver: 7.7.3 - svelte-eslint-parser: 1.4.0(svelte@5.45.2) + svelte-eslint-parser: 1.4.1(svelte@5.45.5) optionalDependencies: - svelte: 5.45.2 + svelte: 5.45.5 transitivePeerDependencies: - ts-node @@ -19034,7 +19042,7 @@ snapshots: dependencies: estraverse: 5.3.0 - esrap@2.2.0: + esrap@2.2.1: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -19095,7 +19103,7 @@ snapshots: eval@0.1.8: dependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.2 require-like: 0.1.2 event-emitter@0.3.5: @@ -19217,7 +19225,7 @@ snapshots: transitivePeerDependencies: - supports-color - express@5.2.0: + express@5.2.1: dependencies: accepts: 2.0.0 body-parser: 2.2.1 @@ -19694,9 +19702,9 @@ snapshots: optionalDependencies: uglify-js: 3.19.3 - happy-dom@20.0.10: + happy-dom@20.0.11: dependencies: - '@types/node': 20.19.24 + '@types/node': 20.19.26 '@types/whatwg-mimetype': 3.0.2 whatwg-mimetype: 3.0.0 @@ -20134,9 +20142,9 @@ snapshots: inline-style-parser@0.2.7: {} - inquirer@8.2.7(@types/node@24.10.1): + inquirer@8.2.7(@types/node@24.10.2): dependencies: - '@inquirer/external-editor': 1.0.3(@types/node@24.10.1) + '@inquirer/external-editor': 1.0.3(@types/node@24.10.2) ansi-escapes: 4.3.2 chalk: 4.1.2 cli-cursor: 3.1.0 @@ -20350,7 +20358,7 @@ snapshots: jest-util@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 24.10.1 + '@types/node': 24.10.2 chalk: 4.1.2 ci-info: 3.9.0 graceful-fs: 4.2.11 @@ -20358,13 +20366,13 @@ snapshots: jest-worker@27.5.1: dependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.2 merge-stream: 2.0.0 supports-color: 8.1.1 jest-worker@29.7.0: dependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.2 jest-util: 29.7.0 merge-stream: 2.0.0 supports-color: 8.1.1 @@ -20416,7 +20424,7 @@ snapshots: http-proxy-agent: 5.0.0 https-proxy-agent: 5.0.1 is-potential-custom-element-name: 1.0.1 - nwsapi: 2.2.22 + nwsapi: 2.2.23 parse5: 7.3.0 saxes: 6.0.0 symbol-tree: 3.2.4 @@ -20445,7 +20453,7 @@ snapshots: http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.6 is-potential-custom-element-name: 1.0.1 - nwsapi: 2.2.22 + nwsapi: 2.2.23 parse5: 7.3.0 rrweb-cssom: 0.8.0 saxes: 6.0.0 @@ -20475,7 +20483,7 @@ snapshots: http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.6 is-potential-custom-element-name: 1.0.1 - nwsapi: 2.2.22 + nwsapi: 2.2.23 parse5: 7.3.0 rrweb-cssom: 0.8.0 saxes: 6.0.0 @@ -20522,9 +20530,9 @@ snapshots: optionalDependencies: graceful-fs: 4.2.11 - jsonwebtoken@9.0.2: + jsonwebtoken@9.0.3: dependencies: - jws: 3.2.2 + jws: 4.0.1 lodash.includes: 4.3.0 lodash.isboolean: 3.0.3 lodash.isinteger: 4.0.4 @@ -20539,15 +20547,15 @@ snapshots: justified-layout@4.1.0: {} - jwa@1.4.2: + jwa@2.0.1: dependencies: buffer-equal-constant-time: 1.0.1 ecdsa-sig-formatter: 1.0.11 safe-buffer: 5.2.1 - jws@3.2.2: + jws@4.0.1: dependencies: - jwa: 1.4.2 + jwa: 2.0.1 safe-buffer: 5.2.1 kdbush@3.0.0: {} @@ -20852,7 +20860,7 @@ snapshots: tinyqueue: 2.0.3 vt-pbf: 3.1.3 - maplibre-gl@5.13.0: + maplibre-gl@5.14.0: dependencies: '@mapbox/geojson-rewind': 0.5.2 '@mapbox/jsonlint-lines-primitives': 2.0.2 @@ -20862,8 +20870,8 @@ snapshots: '@mapbox/vector-tile': 2.0.4 '@mapbox/whoots-js': 3.1.0 '@maplibre/maplibre-gl-style-spec': 24.3.1 - '@maplibre/mlt': 1.1.0 - '@maplibre/vt-pbf': 4.0.3 + '@maplibre/mlt': 1.1.2 + '@maplibre/vt-pbf': 4.1.0 '@types/geojson': 7946.0.16 '@types/geojson-vt': 3.2.5 '@types/supercluster': 7.1.3 @@ -21621,7 +21629,7 @@ snapshots: neo-async@2.6.2: {} - nest-commander@3.20.1(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(@types/inquirer@8.2.11)(@types/node@24.10.1)(typescript@5.9.3): + nest-commander@3.20.1(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(@types/inquirer@8.2.11)(@types/node@24.10.2)(typescript@5.9.3): dependencies: '@fig/complete-commander': 3.2.0(commander@11.1.0) '@golevelup/nestjs-discovery': 5.0.0(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9) @@ -21630,7 +21638,7 @@ snapshots: '@types/inquirer': 8.2.11 commander: 11.1.0 cosmiconfig: 8.3.6(typescript@5.9.3) - inquirer: 8.2.7(@types/node@24.10.1) + inquirer: 8.2.7(@types/node@24.10.2) transitivePeerDependencies: - '@types/node' - typescript @@ -21767,7 +21775,7 @@ snapshots: schema-utils: 3.3.0 webpack: 5.103.0 - nwsapi@2.2.22: + nwsapi@2.2.23: optional: true nypm@0.6.0: @@ -22141,7 +22149,7 @@ snapshots: postcss-attribute-case-insensitive@7.0.1(postcss@8.5.6): dependencies: postcss: 8.5.6 - postcss-selector-parser: 7.1.0 + postcss-selector-parser: 7.1.1 postcss-calc@9.0.1(postcss@8.5.6): dependencies: @@ -22212,12 +22220,12 @@ snapshots: '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 postcss: 8.5.6 - postcss-selector-parser: 7.1.0 + postcss-selector-parser: 7.1.1 postcss-dir-pseudo-class@9.0.1(postcss@8.5.6): dependencies: postcss: 8.5.6 - postcss-selector-parser: 7.1.0 + postcss-selector-parser: 7.1.1 postcss-discard-comments@6.0.2(postcss@8.5.6): dependencies: @@ -22250,12 +22258,12 @@ snapshots: postcss-focus-visible@10.0.1(postcss@8.5.6): dependencies: postcss: 8.5.6 - postcss-selector-parser: 7.1.0 + postcss-selector-parser: 7.1.1 postcss-focus-within@9.0.1(postcss@8.5.6): dependencies: postcss: 8.5.6 - postcss-selector-parser: 7.1.0 + postcss-selector-parser: 7.1.1 postcss-font-variant@5.0.0(postcss@8.5.6): dependencies: @@ -22374,13 +22382,13 @@ snapshots: dependencies: icss-utils: 5.1.0(postcss@8.5.6) postcss: 8.5.6 - postcss-selector-parser: 7.1.0 + postcss-selector-parser: 7.1.1 postcss-value-parser: 4.2.0 postcss-modules-scope@3.2.1(postcss@8.5.6): dependencies: postcss: 8.5.6 - postcss-selector-parser: 7.1.0 + postcss-selector-parser: 7.1.1 postcss-modules-values@4.0.0(postcss@8.5.6): dependencies: @@ -22394,10 +22402,10 @@ snapshots: postcss-nesting@13.0.2(postcss@8.5.6): dependencies: - '@csstools/selector-resolve-nested': 3.1.0(postcss-selector-parser@7.1.0) - '@csstools/selector-specificity': 5.0.0(postcss-selector-parser@7.1.0) + '@csstools/selector-resolve-nested': 3.1.0(postcss-selector-parser@7.1.1) + '@csstools/selector-specificity': 5.0.0(postcss-selector-parser@7.1.1) postcss: 8.5.6 - postcss-selector-parser: 7.1.0 + postcss-selector-parser: 7.1.1 postcss-normalize-charset@6.0.2(postcss@8.5.6): dependencies: @@ -22542,7 +22550,7 @@ snapshots: postcss-pseudo-class-any-link@10.0.1(postcss@8.5.6): dependencies: postcss: 8.5.6 - postcss-selector-parser: 7.1.0 + postcss-selector-parser: 7.1.1 postcss-reduce-idents@6.0.3(postcss@8.5.6): dependencies: @@ -22575,14 +22583,14 @@ snapshots: postcss-selector-not@8.0.1(postcss@8.5.6): dependencies: postcss: 8.5.6 - postcss-selector-parser: 7.1.0 + postcss-selector-parser: 7.1.1 postcss-selector-parser@6.1.2: dependencies: cssesc: 3.0.0 util-deprecate: 1.0.2 - postcss-selector-parser@7.1.0: + postcss-selector-parser@7.1.1: dependencies: cssesc: 3.0.0 util-deprecate: 1.0.2 @@ -22646,10 +22654,10 @@ snapshots: dependencies: prettier: 3.7.4 - prettier-plugin-svelte@3.4.0(prettier@3.7.4)(svelte@5.45.2): + prettier-plugin-svelte@3.4.0(prettier@3.7.4)(svelte@5.45.5): dependencies: prettier: 3.7.4 - svelte: 5.45.2 + svelte: 5.45.5 prettier@3.7.4: {} @@ -22728,7 +22736,7 @@ snapshots: '@protobufjs/path': 1.1.2 '@protobufjs/pool': 1.1.0 '@protobufjs/utf8': 1.1.0 - '@types/node': 24.10.1 + '@types/node': 24.10.2 long: 5.3.2 protocol-buffers-schema@3.6.0: {} @@ -22827,9 +22835,9 @@ snapshots: react: 18.3.1 scheduler: 0.23.2 - react-dom@19.2.0(react@19.2.0): + react-dom@19.2.1(react@19.2.1): dependencies: - react: 19.2.0 + react: 19.2.1 scheduler: 0.27.0 react-email@4.3.2: @@ -22909,7 +22917,7 @@ snapshots: dependencies: loose-envify: 1.4.0 - react@19.2.0: {} + react@19.2.1: {} read-cache@1.0.0: dependencies: @@ -23251,14 +23259,14 @@ snapshots: dependencies: queue-microtask: 1.2.3 - runed@0.35.1(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2): + runed@0.35.1(@sveltejs/kit@2.49.1(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.5)(vite@7.2.6(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.5)(vite@7.2.6(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.5): dependencies: dequal: 2.0.3 esm-env: 1.2.2 lz-string: 1.5.0 - svelte: 5.45.2 + svelte: 5.45.5 optionalDependencies: - '@sveltejs/kit': 2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)) + '@sveltejs/kit': 2.49.1(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.5)(vite@7.2.6(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.5)(vite@7.2.6(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)) rw@1.3.3: {} @@ -23896,28 +23904,28 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - svelte-check@4.3.4(picomatch@4.0.3)(svelte@5.45.2)(typescript@5.9.3): + svelte-check@4.3.4(picomatch@4.0.3)(svelte@5.45.5)(typescript@5.9.3): dependencies: '@jridgewell/trace-mapping': 0.3.31 chokidar: 4.0.3 fdir: 6.5.0(picomatch@4.0.3) picocolors: 1.1.1 sade: 1.8.1 - svelte: 5.45.2 + svelte: 5.45.5 typescript: 5.9.3 transitivePeerDependencies: - picomatch - svelte-eslint-parser@1.4.0(svelte@5.45.2): + svelte-eslint-parser@1.4.1(svelte@5.45.5): dependencies: eslint-scope: 8.4.0 eslint-visitor-keys: 4.2.1 espree: 10.4.0 postcss: 8.5.6 postcss-scss: 4.0.9(postcss@8.5.6) - postcss-selector-parser: 7.1.0 + postcss-selector-parser: 7.1.1 optionalDependencies: - svelte: 5.45.2 + svelte: 5.45.5 svelte-gestures@5.2.2: {} @@ -23925,7 +23933,7 @@ snapshots: dependencies: highlight.js: 11.11.1 - svelte-i18n@4.0.1(svelte@5.45.2): + svelte-i18n@4.0.1(svelte@5.45.5): dependencies: cli-color: 2.0.4 deepmerge: 4.3.1 @@ -23933,36 +23941,36 @@ snapshots: estree-walker: 2.0.2 intl-messageformat: 10.7.18 sade: 1.8.1 - svelte: 5.45.2 + svelte: 5.45.5 tiny-glob: 0.2.9 - svelte-maplibre@1.2.5(svelte@5.45.2): + svelte-maplibre@1.2.5(svelte@5.45.5): dependencies: d3-geo: 3.1.1 dequal: 2.0.3 just-compare: 2.3.0 - maplibre-gl: 5.13.0 + maplibre-gl: 5.14.0 pmtiles: 3.2.1 - svelte: 5.45.2 + svelte: 5.45.5 - svelte-parse-markup@0.1.5(svelte@5.45.2): + svelte-parse-markup@0.1.5(svelte@5.45.5): dependencies: - svelte: 5.45.2 + svelte: 5.45.5 - svelte-persisted-store@0.12.0(svelte@5.45.2): + svelte-persisted-store@0.12.0(svelte@5.45.5): dependencies: - svelte: 5.45.2 + svelte: 5.45.5 - svelte-toolbelt@0.10.6(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2): + svelte-toolbelt@0.10.6(@sveltejs/kit@2.49.1(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.5)(vite@7.2.6(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.5)(vite@7.2.6(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.5): dependencies: clsx: 2.1.1 - runed: 0.35.1(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2) + runed: 0.35.1(@sveltejs/kit@2.49.1(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.5)(vite@7.2.6(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.5)(vite@7.2.6(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.5) style-to-object: 1.0.14 - svelte: 5.45.2 + svelte: 5.45.5 transitivePeerDependencies: - '@sveltejs/kit' - svelte@5.45.2: + svelte@5.45.5: dependencies: '@jridgewell/remapping': 2.3.5 '@jridgewell/sourcemap-codec': 1.5.5 @@ -23972,9 +23980,9 @@ snapshots: aria-query: 5.3.2 axobject-query: 4.1.0 clsx: 2.1.1 - devalue: 5.5.0 + devalue: 5.6.0 esm-env: 1.2.2 - esrap: 2.2.0 + esrap: 2.2.1 is-reference: 3.0.3 locate-character: 3.0.0 magic-string: 0.30.21 @@ -24364,12 +24372,12 @@ snapshots: typedarray@0.0.6: {} - typescript-eslint@8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3): + typescript-eslint@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.48.0(@typescript-eslint/parser@8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/parser': 8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/typescript-estree': 8.48.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/eslint-plugin': 8.48.1(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/parser': 8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.48.1(typescript@5.9.3) + '@typescript-eslint/utils': 8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) eslint: 9.39.1(jiti@2.6.1) typescript: 5.9.3 transitivePeerDependencies: @@ -24643,13 +24651,13 @@ snapshots: - rollup - supports-color - vite-node@3.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2): + vite-node@3.2.4(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2): dependencies: cac: 6.7.14 debug: 4.4.3 es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2) + vite: 7.2.6(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2) transitivePeerDependencies: - '@types/node' - jiti @@ -24664,18 +24672,18 @@ snapshots: - tsx - yaml - vite-tsconfig-paths@5.1.4(typescript@5.9.3)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)): + vite-tsconfig-paths@5.1.4(typescript@5.9.3)(vite@7.2.6(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)): dependencies: debug: 4.4.3 globrex: 0.1.2 tsconfck: 3.1.6(typescript@5.9.3) optionalDependencies: - vite: 7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2) + vite: 7.2.6(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2) transitivePeerDependencies: - supports-color - typescript - vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2): + vite@7.2.6(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2): dependencies: esbuild: 0.25.12 fdir: 6.5.0(picomatch@4.0.3) @@ -24684,26 +24692,26 @@ snapshots: rollup: 4.53.3 tinyglobby: 0.2.15 optionalDependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.2 fsevents: 2.3.3 jiti: 2.6.1 lightningcss: 1.30.2 terser: 5.44.0 yaml: 2.8.2 - vitefu@1.1.1(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)): + vitefu@1.1.1(vite@7.2.6(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)): optionalDependencies: - vite: 7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2) + vite: 7.2.6(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2) - vitest-fetch-mock@0.4.5(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.10.1)(happy-dom@20.0.10)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)): + vitest-fetch-mock@0.4.5(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.10.2)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)): dependencies: - vitest: 3.2.4(@types/debug@4.1.12)(@types/node@24.10.1)(happy-dom@20.0.10)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2) + vitest: 3.2.4(@types/debug@4.1.12)(@types/node@24.10.2)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2) - vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.10.1)(happy-dom@20.0.10)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2(encoding@0.1.13)))(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2): + vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.10.2)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2(encoding@0.1.13)))(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2): dependencies: '@types/chai': 5.2.2 '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)) + '@vitest/mocker': 3.2.4(vite@7.2.6(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)) '@vitest/pretty-format': 3.2.4 '@vitest/runner': 3.2.4 '@vitest/snapshot': 3.2.4 @@ -24721,13 +24729,13 @@ snapshots: tinyglobby: 0.2.15 tinypool: 1.1.1 tinyrainbow: 2.0.0 - vite: 7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2) - vite-node: 3.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2) + vite: 7.2.6(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2) + vite-node: 3.2.4(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2) why-is-node-running: 2.3.0 optionalDependencies: '@types/debug': 4.1.12 - '@types/node': 24.10.1 - happy-dom: 20.0.10 + '@types/node': 24.10.2 + happy-dom: 20.0.11 jsdom: 26.1.0(canvas@2.11.2(encoding@0.1.13)) transitivePeerDependencies: - jiti @@ -24743,11 +24751,11 @@ snapshots: - tsx - yaml - vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.10.1)(happy-dom@20.0.10)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2): + vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.10.2)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2): dependencies: '@types/chai': 5.2.2 '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)) + '@vitest/mocker': 3.2.4(vite@7.2.6(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)) '@vitest/pretty-format': 3.2.4 '@vitest/runner': 3.2.4 '@vitest/snapshot': 3.2.4 @@ -24765,13 +24773,13 @@ snapshots: tinyglobby: 0.2.15 tinypool: 1.1.1 tinyrainbow: 2.0.0 - vite: 7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2) - vite-node: 3.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2) + vite: 7.2.6(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2) + vite-node: 3.2.4(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2) why-is-node-running: 2.3.0 optionalDependencies: '@types/debug': 4.1.12 - '@types/node': 24.10.1 - happy-dom: 20.0.10 + '@types/node': 24.10.2 + happy-dom: 20.0.11 jsdom: 26.1.0(canvas@2.11.2) transitivePeerDependencies: - jiti diff --git a/web/package.json b/web/package.json index b3cc6d2c2f..96aba1660e 100644 --- a/web/package.json +++ b/web/package.json @@ -9,7 +9,7 @@ "build:stats": "BUILD_STATS=true vite build", "package": "svelte-kit package", "preview": "vite preview", - "check:svelte": "svelte-check --no-tsconfig --fail-on-warnings", + "check:svelte": "svelte-check --no-tsconfig --fail-on-warnings --compiler-warnings 'state_referenced_locally:ignore'", "check:typescript": "tsc --noEmit", "check:watch": "pnpm run check:svelte --watch", "check:code": "pnpm run format && pnpm run lint && pnpm run check:svelte && pnpm run check:typescript", @@ -98,7 +98,7 @@ "prettier-plugin-sort-json": "^4.1.1", "prettier-plugin-svelte": "^3.3.3", "rollup-plugin-visualizer": "^6.0.0", - "svelte": "5.45.2", + "svelte": "5.45.5", "svelte-check": "^4.1.5", "svelte-eslint-parser": "^1.3.3", "tailwindcss": "^4.1.7", From e6373870826a32044696ec6e0af83290c1fbb44c Mon Sep 17 00:00:00 2001 From: hubert-taieb <69033160+hubert-taieb@users.noreply.github.com> Date: Thu, 11 Dec 2025 10:18:19 -0500 Subject: [PATCH 29/35] fix(server): prevent metadata extraction failures on large video files (#24094) * prevent metadata extraction failures on large video files Increases ExifTool timeout from 20s to 120s to prevent GPS metadata extraction failures on large video files (>2GB, 10+ minutes). Issue: Large videos timeout during metadata extraction, causing GPS coordinates to be lost even though ExifTool can extract them given enough time. Testing: 2.6GB, 10:52min video that previously timed out now successfully extracts GPS metadata. * redundant comment Increased task timeout for processing large videos. * chore: lint --------- Co-authored-by: Mert <101130780+mertalev@users.noreply.github.com> Co-authored-by: Alex Tran --- server/src/repositories/metadata.repository.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/server/src/repositories/metadata.repository.ts b/server/src/repositories/metadata.repository.ts index 32882de0e0..1334d1220f 100644 --- a/server/src/repositories/metadata.repository.ts +++ b/server/src/repositories/metadata.repository.ts @@ -89,6 +89,7 @@ export class MetadataRepository { // Enable exiftool LFS to parse metadata for files larger than 2GB. readArgs: ['-api', 'largefilesupport=1'], writeArgs: ['-api', 'largefilesupport=1', '-overwrite_original'], + taskTimeoutMillis: 2 * 60 * 1000, }); constructor(private logger: LoggingRepository) { From f73511a754833d185ce4b71e510489dfdf3bb10a Mon Sep 17 00:00:00 2001 From: Bart van Velden <2493558+bartvanvelden@users.noreply.github.com> Date: Thu, 11 Dec 2025 16:19:33 +0100 Subject: [PATCH 30/35] fix(docs): typo in maintenance mode command (#24518) --- docs/docs/administration/server-commands.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/administration/server-commands.md b/docs/docs/administration/server-commands.md index 8c58baac17..cbd029296f 100644 --- a/docs/docs/administration/server-commands.md +++ b/docs/docs/administration/server-commands.md @@ -52,7 +52,7 @@ Password login has been enabled. Disable Maintenance Mode ``` -immich-admin disable-maintenace-mode +immich-admin disable-maintenance-mode Maintenance mode has been disabled. ``` From 4c0a41723ff0b707f9c3a6053abb1ee0b00e9976 Mon Sep 17 00:00:00 2001 From: Yaros Date: Thu, 11 Dec 2025 16:20:29 +0100 Subject: [PATCH 31/35] feat(web): asset selection bar in tags view (#24522) * feat(web): asset selection tab in tags view * chore: remove unused imports --- .../[[assetId=id]]/+page.svelte | 64 ++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) diff --git a/web/src/routes/(user)/tags/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/tags/[[photos=photos]]/[[assetId=id]]/+page.svelte index 442d3cef6c..7f873aa918 100644 --- a/web/src/routes/(user)/tags/[[photos=photos]]/[[assetId=id]]/+page.svelte +++ b/web/src/routes/(user)/tags/[[photos=photos]]/[[assetId=id]]/+page.svelte @@ -15,9 +15,24 @@ import { joinPaths, TreeNode } from '$lib/utils/tree-utils'; import { deleteTag, getAllTags, type TagResponseDto } from '@immich/sdk'; import { Button, HStack, modalManager, Text } from '@immich/ui'; - import { mdiPencil, mdiPlus, mdiTag, mdiTagMultiple, mdiTrashCanOutline } from '@mdi/js'; + import { mdiDotsVertical, mdiPencil, mdiPlus, mdiTag, mdiTagMultiple, mdiTrashCanOutline } from '@mdi/js'; import { t } from 'svelte-i18n'; import type { PageData } from './$types'; + import AssetSelectControlBar from '$lib/components/timeline/AssetSelectControlBar.svelte'; + import AddToAlbum from '$lib/components/timeline/actions/AddToAlbumAction.svelte'; + import ArchiveAction from '$lib/components/timeline/actions/ArchiveAction.svelte'; + import ChangeDate from '$lib/components/timeline/actions/ChangeDateAction.svelte'; + import ChangeDescription from '$lib/components/timeline/actions/ChangeDescriptionAction.svelte'; + import ChangeLocation from '$lib/components/timeline/actions/ChangeLocationAction.svelte'; + import CreateSharedLink from '$lib/components/timeline/actions/CreateSharedLinkAction.svelte'; + import DeleteAssets from '$lib/components/timeline/actions/DeleteAssetsAction.svelte'; + import DownloadAction from '$lib/components/timeline/actions/DownloadAction.svelte'; + import FavoriteAction from '$lib/components/timeline/actions/FavoriteAction.svelte'; + import SelectAllAssets from '$lib/components/timeline/actions/SelectAllAction.svelte'; + import SetVisibilityAction from '$lib/components/timeline/actions/SetVisibilityAction.svelte'; + import TagAction from '$lib/components/timeline/actions/TagAction.svelte'; + import ButtonContextMenu from '$lib/components/shared-components/context-menu/button-context-menu.svelte'; + import { preferences, user } from '$lib/stores/user.store'; interface Props { data: PageData; @@ -79,6 +94,11 @@ // navigate to parent await navigateToView(tag.parent ? tag.parent.path : ''); }; + + const handleSetVisibility = (assetIds: string[]) => { + timelineManager.removeAssets(assetIds); + assetInteraction.clearMultiselect(); + }; @@ -131,3 +151,45 @@ {/if} + +
+ {#if assetInteraction.selectionActive} +
+ assetInteraction.clearMultiselect()} + > + + + + + + + timelineManager.update(ids, (asset) => (asset.isFavorite = isFavorite))} + > + + + + + + timelineManager.update(ids, (asset) => (asset.visibility = visibility))} + /> + {#if $preferences.tags.enabled} + + {/if} + timelineManager.removeAssets(assetIds)} + onUndoDelete={(assets) => timelineManager.upsertAssets(assets)} + /> + + + +
+ {/if} +
From c5d99711f73455531d8b01fd7f707c7f315dc1fa Mon Sep 17 00:00:00 2001 From: Sergey Katsubo Date: Thu, 11 Dec 2025 18:20:51 +0300 Subject: [PATCH 32/35] fix(web): show inferred timezone in date editor (#24513) fix(web): show inferred timezone of asset in date editor --- web/src/lib/components/asset-viewer/detail-panel.svelte | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/web/src/lib/components/asset-viewer/detail-panel.svelte b/web/src/lib/components/asset-viewer/detail-panel.svelte index 60913ff47b..2dde2c35ee 100644 --- a/web/src/lib/components/asset-viewer/detail-panel.svelte +++ b/web/src/lib/components/asset-viewer/detail-panel.svelte @@ -114,7 +114,11 @@ return; } - await modalManager.show(AssetChangeDateModal, { asset: toTimelineAsset(asset), initialDate: dateTime }); + await modalManager.show(AssetChangeDateModal, { + asset: toTimelineAsset(asset), + initialDate: dateTime, + initialTimeZone: timeZone, + }); }; From 59a3f0f455b9487a40f6518e3abb61d4a9758312 Mon Sep 17 00:00:00 2001 From: Yaros Date: Thu, 11 Dec 2025 16:47:31 +0100 Subject: [PATCH 33/35] feat(mobile): create new album from add to modal (#24431) * feat(mobile): create new album from add to modal * refactor: use statefulwidget instead of hook * chore: rename createalbumbutton --- .../add_action_button.widget.dart | 5 +- .../widgets/album/album_selector.widget.dart | 60 +++++++++++++++++++ .../album/new_album_name_modal.widget.dart | 53 ++++++++++++++++ 3 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 mobile/lib/presentation/widgets/album/new_album_name_modal.widget.dart diff --git a/mobile/lib/presentation/widgets/action_buttons/add_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/add_action_button.widget.dart index 08ac9f982c..307d524480 100644 --- a/mobile/lib/presentation/widgets/action_buttons/add_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/add_action_button.widget.dart @@ -109,7 +109,10 @@ class _AddActionButtonState extends ConsumerState { return; } - final List slivers = [AlbumSelector(onAlbumSelected: (album) => _addCurrentAssetToAlbum(album))]; + final List slivers = [ + const CreateAlbumButton(), + AlbumSelector(onAlbumSelected: (album) => _addCurrentAssetToAlbum(album)), + ]; showModalBottomSheet( context: context, diff --git a/mobile/lib/presentation/widgets/album/album_selector.widget.dart b/mobile/lib/presentation/widgets/album/album_selector.widget.dart index 4110966e57..8a3de2509c 100644 --- a/mobile/lib/presentation/widgets/album/album_selector.widget.dart +++ b/mobile/lib/presentation/widgets/album/album_selector.widget.dart @@ -12,8 +12,10 @@ import 'package:immich_mobile/extensions/theme_extensions.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/models/albums/album_search.model.dart'; import 'package:immich_mobile/presentation/widgets/album/album_tile.dart'; +import 'package:immich_mobile/presentation/widgets/album/new_album_name_modal.widget.dart'; import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart'; import 'package:immich_mobile/providers/infrastructure/album.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/asset_viewer/current_asset.provider.dart'; import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart'; @@ -766,3 +768,61 @@ class AddToAlbumHeader extends ConsumerWidget { ); } } + +class CreateAlbumButton extends ConsumerWidget { + const CreateAlbumButton({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + Future onCreateAlbum() async { + var albumName = await showDialog(context: context, builder: (context) => const NewAlbumNameModal()); + if (albumName == null) return; + final asset = ref.read(currentAssetNotifier); + + if (asset == null) { + ImmichToast.show(context: context, msg: "Cannot load asset information.", toastType: ToastType.error); + return; + } + + final album = await ref + .read(remoteAlbumProvider.notifier) + .createAlbum(title: albumName, assetIds: [asset.remoteId!]); + + if (album == null) { + ImmichToast.show(context: context, toastType: ToastType.error, msg: 'errors.failed_to_create_album'.tr()); + return; + } + + ImmichToast.show( + context: context, + msg: 'add_to_album_bottom_sheet_added'.tr(namedArgs: {'album': album.name}), + ); + context.pop(); + } + + return SliverPadding( + padding: const EdgeInsets.symmetric(horizontal: 16), + sliver: SliverToBoxAdapter( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("add_to_album", style: context.textTheme.titleSmall).tr(), + TextButton.icon( + style: TextButton.styleFrom( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + minimumSize: const Size(0, 0), + tapTargetSize: MaterialTapTargetSize.shrinkWrap, + ), + onPressed: onCreateAlbum, + icon: Icon(Icons.add, color: context.primaryColor), + label: Text( + "common_create_new_album", + style: TextStyle(color: context.primaryColor, fontWeight: FontWeight.bold, fontSize: 14), + ).tr(), + ), + ], + ), + ), + ); + } +} diff --git a/mobile/lib/presentation/widgets/album/new_album_name_modal.widget.dart b/mobile/lib/presentation/widgets/album/new_album_name_modal.widget.dart new file mode 100644 index 0000000000..a5e21af489 --- /dev/null +++ b/mobile/lib/presentation/widgets/album/new_album_name_modal.widget.dart @@ -0,0 +1,53 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; + +class NewAlbumNameModal extends StatefulWidget { + const NewAlbumNameModal({super.key}); + + @override + State createState() => _NewAlbumNameModalState(); +} + +class _NewAlbumNameModalState extends State { + TextEditingController nameController = TextEditingController(); + + @override + void dispose() { + nameController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: const Text("album_name", style: TextStyle(fontWeight: FontWeight.bold)).tr(), + content: SingleChildScrollView( + child: TextFormField( + controller: nameController, + textCapitalization: TextCapitalization.words, + autofocus: true, + decoration: InputDecoration(hintText: 'name'.tr(), border: const OutlineInputBorder()), + ), + ), + actions: [ + TextButton( + onPressed: () => context.pop(null), + child: Text( + "cancel", + style: TextStyle(color: Colors.red[300], fontWeight: FontWeight.bold), + ).tr(), + ), + TextButton( + onPressed: () { + context.pop(nameController.text.trim()); + }, + child: Text( + "create_album", + style: TextStyle(color: context.primaryColor, fontWeight: FontWeight.bold), + ).tr(), + ), + ], + ); + } +} From 8cf900bafaa741316cb6813a61306477405dc194 Mon Sep 17 00:00:00 2001 From: Yaros Date: Thu, 11 Dec 2025 17:57:37 +0100 Subject: [PATCH 34/35] fix(mobile): local videos with '#' don't play on android (#24373) * fix(mobile): videos with '#' don't play on android * refactor: one line Co-authored-by: shenlong <139912620+shenlong-tanwen@users.noreply.github.com> * fix: depend on platform --------- Co-authored-by: shenlong <139912620+shenlong-tanwen@users.noreply.github.com> --- .../widgets/asset_viewer/video_viewer.widget.dart | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/mobile/lib/presentation/widgets/asset_viewer/video_viewer.widget.dart b/mobile/lib/presentation/widgets/asset_viewer/video_viewer.widget.dart index 08b5b25343..8727f40a1a 100644 --- a/mobile/lib/presentation/widgets/asset_viewer/video_viewer.widget.dart +++ b/mobile/lib/presentation/widgets/asset_viewer/video_viewer.widget.dart @@ -9,6 +9,7 @@ import 'package:immich_mobile/domain/models/setting.model.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/domain/services/setting.service.dart'; import 'package:immich_mobile/entities/store.entity.dart'; +import 'package:immich_mobile/extensions/platform_extensions.dart'; import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart'; import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.state.dart'; import 'package:immich_mobile/presentation/widgets/asset_viewer/video_viewer_controls.widget.dart'; @@ -104,7 +105,12 @@ class NativeVideoViewer extends HookConsumerWidget { throw Exception('No file found for the video'); } - final source = await VideoSource.init(path: file.path, type: VideoSourceType.file); + // Pass a file:// URI so Android's Uri.parse doesn't + // interpret characters like '#' as fragment identifiers. + final source = await VideoSource.init( + path: CurrentPlatform.isAndroid ? file.uri.toString() : file.path, + type: VideoSourceType.file, + ); return source; } From 4b345e02ffe1cb0943969bd91bf15907bd757a8f Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 11 Dec 2025 11:06:53 -0600 Subject: [PATCH 35/35] fix: refresh appear in list after asset is added to a current or new album (#24523) --- .../widgets/action_buttons/add_action_button.widget.dart | 3 +++ .../widgets/album/album_selector.widget.dart | 9 ++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/mobile/lib/presentation/widgets/action_buttons/add_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/add_action_button.widget.dart index 307d524480..23cd19f363 100644 --- a/mobile/lib/presentation/widgets/action_buttons/add_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/add_action_button.widget.dart @@ -156,6 +156,9 @@ class _AddActionButtonState extends ConsumerState { context: context, msg: 'add_to_album_bottom_sheet_added'.tr(namedArgs: {'album': album.name}), ); + + // Invalidate using the asset's remote ID to refresh the "Appears in" list + ref.invalidate(albumsContainingAssetProvider(latest.remoteId!)); } if (!context.mounted) { diff --git a/mobile/lib/presentation/widgets/album/album_selector.widget.dart b/mobile/lib/presentation/widgets/album/album_selector.widget.dart index 8a3de2509c..c42f49091f 100644 --- a/mobile/lib/presentation/widgets/album/album_selector.widget.dart +++ b/mobile/lib/presentation/widgets/album/album_selector.widget.dart @@ -776,7 +776,10 @@ class CreateAlbumButton extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { Future onCreateAlbum() async { var albumName = await showDialog(context: context, builder: (context) => const NewAlbumNameModal()); - if (albumName == null) return; + if (albumName == null) { + return; + } + final asset = ref.read(currentAssetNotifier); if (asset == null) { @@ -797,6 +800,10 @@ class CreateAlbumButton extends ConsumerWidget { context: context, msg: 'add_to_album_bottom_sheet_added'.tr(namedArgs: {'album': album.name}), ); + + // Invalidate using the asset's remote ID to refresh the "Appears in" list + ref.invalidate(albumsContainingAssetProvider(asset.remoteId!)); + context.pop(); }