diff --git a/CHANGELOG.md b/CHANGELOG.md index 20d9ba5..1f0ad30 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,37 @@ # Changelog +## 1.4.0 (2026-03-23) + +### Features + +- **Folder management**: Create, rename, move, and delete mailbox folders from the sidebar + context menu, with drag-and-drop reparenting and inline editing (#44) +- **Mail multi-selection**: Select multiple emails with checkboxes or shift-click, then + batch move or delete from the toolbar. Includes a "Move to" popover with search and + keyboard navigation (#43) + +### Fixes + +- **Health endpoint**: Container restarts caused by false-positive memory alerts. The check + was using V8's current heap allocation as the max instead of the real heap limit (#41). + Thanks @wrenix and @ClemaX for reporting and diagnosing. +- **Identity deletion**: Fix "delete identity always failed" by adding the required + `urn:ietf:params:jmap:submission` capability to all Identity and EmailSubmission + operations (#42). Thanks @freddij for reporting. +- **Inline images**: CID-referenced images now render inline instead of showing as + attachments +- **Email list**: Eliminate flicker during loading and after-action refreshes +- **Copy to clipboard**: Visual feedback on copy, fix dark mode background tint +- **Console cleanup**: Remove production console statements + +### Dependencies + +- Next.js 16.2.0 -> 16.2.1 +- Tailwind CSS 4.2.1 -> 4.2.2 +- Zustand 5.0.11 -> 5.0.12 +- typescript-eslint 8.56.1 -> 8.57.1 +- Fix flatted prototype pollution (GHSA-rf6f-7fwh-wjgh) + ## 1.3.3 (2026-03-20) ### Fixes diff --git a/README.md b/README.md index 7c2bb7f..3c979f8 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,9 @@ Stalwart is a mail server written in Rust with native JMAP support, not IMAP/SMT - Responsive (desktop sidebar + mobile bottom tab bar + mobile action bar) - Keyboard shortcuts - Drag-and-drop email organization +- Multi-select emails (checkboxes, shift-click) with batch move/delete toolbar +- Folder management (create, rename, move, delete) from sidebar context menu +- Drag-and-drop folder reparenting - Right-click context menus (long-press on touch devices) - Extra-compact, compact, regular, and comfortable density options - Resizable sidebar (drag, touch, keyboard) diff --git a/ROADMAP.md b/ROADMAP.md index 7363882..4fd5c31 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -44,7 +44,7 @@ This document tracks the development status and planned features for JMAP Webmai - [x] Full-text search - [x] Advanced search with JMAP filter panel, search chips, and cross-mailbox queries - [x] Attachment upload and download -- [x] Batch operations (multi-select) +- [x] Batch operations (multi-select with shift-click, batch move/delete toolbar) - [x] Quick reply form - [x] Email threading (Gmail-style inline expansion) @@ -65,6 +65,7 @@ This document tracks the development status and planned features for JMAP Webmai - [x] Drag-and-drop email organization - [x] Right-click context menus - [x] Hierarchical mailbox display +- [x] Folder management (create, rename, move, delete via context menu, drag-and-drop reparenting) - [x] Email list with avatars and visual hierarchy - [x] Expandable email headers - [x] External content warning banner @@ -246,6 +247,7 @@ This document tracks the development status and planned features for JMAP Webmai - [x] Unit tests for OAuth PKCE and discovery (14 tests) - [x] Unit tests for iframe rendering detection (12 tests) - [x] Unit tests for API retry with backoff (9 tests) +- [x] 704 tests total across 28 test suites - [x] XSS attack vector testing - [x] Playwright E2E framework setup diff --git a/VERSION b/VERSION index 31e5c84..88c5fb8 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.3.3 +1.4.0 diff --git a/app/[locale]/auth/callback/page.tsx b/app/[locale]/auth/callback/page.tsx index ed74078..23b7c02 100644 --- a/app/[locale]/auth/callback/page.tsx +++ b/app/[locale]/auth/callback/page.tsx @@ -61,7 +61,7 @@ function OAuthCallbackInner() { .catch(() => { setError("token_exchange_failed"); }); - }, []); // eslint-disable-line react-hooks/exhaustive-deps + }, []); // eslint-disable-line react-hooks/exhaustive-deps -- OAuth callback must run exactly once on mount if (error) { return ( diff --git a/app/[locale]/contacts/page.tsx b/app/[locale]/contacts/page.tsx index 1f55a87..bf33341 100644 --- a/app/[locale]/contacts/page.tsx +++ b/app/[locale]/contacts/page.tsx @@ -129,8 +129,7 @@ export default function ContactsPage() { } toast.success(t("toast.deleted")); setView("list"); - } catch (error) { - console.error('Failed to delete contact:', error); + } catch { toast.error(t("toast.error_delete")); } }; @@ -206,8 +205,7 @@ export default function ContactsPage() { toast.success(t("toast.deleted")); setSelectedGroupId(null); setView("list"); - } catch (error) { - console.error('Failed to delete group:', error); + } catch { toast.error(t("toast.error_delete")); } }; @@ -241,8 +239,7 @@ export default function ContactsPage() { [memberId] ); toast.success(t("toast.updated")); - } catch (error) { - console.error('Failed to remove group member:', error); + } catch { toast.error(t("toast.error_update")); } }; @@ -265,8 +262,7 @@ export default function ContactsPage() { ); toast.success(t("bulk.deleted", { count: selectedContactIds.size })); setView("list"); - } catch (error) { - console.error('Failed to bulk delete contacts:', error); + } catch { toast.error(t("toast.error_delete")); } }; @@ -298,8 +294,7 @@ export default function ContactsPage() { ); toast.success(t("bulk.added_to_group")); setView("list"); - } catch (error) { - console.error('Failed to add contacts to group:', error); + } catch { toast.error(t("toast.error_update")); } }; diff --git a/app/[locale]/error.tsx b/app/[locale]/error.tsx index 50706cf..a26ab89 100644 --- a/app/[locale]/error.tsx +++ b/app/[locale]/error.tsx @@ -21,7 +21,6 @@ export default function LocaleError({ const router = useRouter(); useEffect(() => { - console.error("Route error:", error); }, [error]); return ( diff --git a/app/[locale]/login/page.tsx b/app/[locale]/login/page.tsx index a497225..16d8550 100644 --- a/app/[locale]/login/page.tsx +++ b/app/[locale]/login/page.tsx @@ -79,7 +79,7 @@ export default function LoginPage() { const usernames = JSON.parse(saved); setSavedUsernames(usernames); } catch { - console.error("Failed to parse saved usernames"); + return; } } }, [serverUrl]); @@ -193,7 +193,7 @@ export default function LoginPage() { try { usernames = JSON.parse(saved); } catch { - console.error("Failed to parse saved usernames"); + usernames = []; } } diff --git a/app/[locale]/page.tsx b/app/[locale]/page.tsx index adaf550..4b8fbca 100644 --- a/app/[locale]/page.tsx +++ b/app/[locale]/page.tsx @@ -83,6 +83,7 @@ export default function Home() { setLoadingEmail, setPushConnected, handleStateChange, + refreshCurrentMailbox, clearNewEmailNotification, markAsSpam, undoSpam, @@ -186,7 +187,7 @@ export default function Home() { onDeselectAll: () => { clearSelection(); }, - // eslint-disable-next-line react-hooks/exhaustive-deps + // eslint-disable-next-line react-hooks/exhaustive-deps -- handlers are recreated each render; only rememoize on data/layout changes }), [emails, selectedEmail, client, selectedMailbox, isMobile, isTablet]); // Initialize keyboard shortcuts @@ -286,8 +287,8 @@ export default function Home() { // Push notifications are optional - don't break the app if they fail debug.log('[Push] Failed to setup push notifications:', error); } - } catch (error) { - console.error('Error loading email data:', error); + } catch { + return; } }; loadData(); @@ -344,7 +345,7 @@ export default function Home() { markAsReadTimeoutRef.current = null; } }; - // eslint-disable-next-line react-hooks/exhaustive-deps + // eslint-disable-next-line react-hooks/exhaustive-deps -- mark-as-read triggers only on email selection, not on config changes }, [selectedEmail?.id]); // Handle new email notifications - play sound @@ -392,10 +393,10 @@ export default function Home() { await sendEmail(client, data.to, data.subject, data.body, data.cc, data.bcc, data.identityId, data.fromEmail, data.draftId, data.fromName); setShowComposer(false); - // Refresh the current mailbox to update the UI - await fetchEmails(client, selectedMailbox); - } catch (error) { - console.error("Failed to send email:", error); + // Silent refresh — no loading indicator flash + await refreshCurrentMailbox(client); + } catch { + return; } }; @@ -404,8 +405,8 @@ export default function Home() { try { await client.deleteEmail(draftId); - } catch (error) { - console.error("Failed to discard draft:", error); + } catch { + return; } }; @@ -437,8 +438,8 @@ export default function Home() { try { await deleteEmail(client, selectedEmail.id); dismissViewer(); - } catch (error) { - console.error("Failed to delete email:", error); + } catch { + return; } }; @@ -451,8 +452,8 @@ export default function Home() { try { await moveToMailbox(client, selectedEmail.id, archiveMailbox.id); dismissViewer(); - } catch (error) { - console.error("Failed to archive email:", error); + } catch { + return; } } }; @@ -462,8 +463,8 @@ export default function Home() { try { await toggleStar(client, selectedEmail.id); - } catch (error) { - console.error("Failed to toggle star:", error); + } catch { + return; } }; @@ -483,16 +484,14 @@ export default function Home() { try { await undoSpam(client, emailId); toastInstance.success(t('notifications.email_moved')); - } catch (_error) { - console.error("Failed to undo spam:", _error); + } catch { toastInstance.error(t('email_viewer.spam.error')); } }, }, duration: 5000, }); - } catch (_error) { - console.error("Failed to mark as spam:", _error); + } catch { const toastInstance = (await import('sonner')).toast; toastInstance.error(t('email_viewer.spam.error')); } @@ -509,8 +508,7 @@ export default function Home() { // Deselect email after moving it out of junk selectEmail(null); - } catch (_error) { - console.error("Failed to restore email:", _error); + } catch { const toastInstance = (await import('sonner')).toast; toastInstance.error(t('email_viewer.spam.error_not_spam')); } @@ -544,10 +542,10 @@ export default function Home() { // Update local state selectEmail(email.id === selectedEmail?.id ? { ...email, keywords } : selectedEmail); - // Refresh emails list to show color in list - await fetchEmails(client, selectedMailbox); - } catch (error) { - console.error("Failed to set color tag:", error); + // Silent refresh to show color in list + await refreshCurrentMailbox(client); + } catch { + return; } }; @@ -609,8 +607,8 @@ export default function Home() { try { await client.downloadBlob(blobId, name, type); - } catch (error) { - console.error("Failed to download attachment:", error); + } catch { + return; } }; @@ -638,8 +636,8 @@ export default function Home() { primaryIdentity?.name || undefined ); - // Refresh emails to show the sent reply - await fetchEmails(client, selectedMailbox); + // Silent refresh to show the sent reply + await refreshCurrentMailbox(client); }; // Show loading state while checking auth @@ -681,8 +679,8 @@ export default function Home() { selectEmail(fullEmail); if (isTablet) setTabletListVisible(false); } - } catch (error) { - console.error('Failed to fetch email content:', error); + } catch { + return; } finally { setLoadingEmail(false); } @@ -711,8 +709,7 @@ export default function Home() { // Fetch complete thread emails const emails = await client.getThreadEmails(thread.threadId); setConversationEmails(emails); - } catch (error) { - console.error('Failed to fetch thread emails:', error); + } catch { // Fall back to thread.emails setConversationEmails(thread.emails); } finally { diff --git a/app/api/health/route.ts b/app/api/health/route.ts index ce374ac..e562ca8 100644 --- a/app/api/health/route.ts +++ b/app/api/health/route.ts @@ -1,10 +1,10 @@ import { NextResponse } from 'next/server'; import { NextRequest } from 'next/server'; +import { getHeapStatistics } from 'v8'; import { logger } from '@/lib/logger'; -// Health check thresholds -const MEMORY_WARNING_THRESHOLD = 0.85; // 85% heap usage -const MEMORY_CRITICAL_THRESHOLD = 0.95; // 95% heap usage +const MEMORY_WARNING_THRESHOLD = 0.85; +const MEMORY_CRITICAL_THRESHOLD = 0.95; interface HealthStatus { status: 'healthy' | 'degraded' | 'unhealthy'; @@ -14,6 +14,7 @@ interface HealthStatus { memory?: { heapUsed: number; heapTotal: number; + heapLimit: number; rss: number; external: number; heapUsagePercent: number; @@ -24,18 +25,6 @@ interface HealthStatus { reason?: string; } -/** - * Health check endpoint for container orchestration - * - * GET /api/health - Basic health check (returns 200 OK or 503 Service Unavailable) - * GET /api/health?detailed=true - Detailed diagnostics with memory stats - * HEAD /api/health - Lightweight health check (status code only) - * - * Health status based on Node.js heap usage: - * - Healthy (200): < 85% heap usage - * - Degraded (200): 85-95% heap usage (warnings in detailed mode) - * - Unhealthy (503): > 95% heap usage - */ export async function GET(request: NextRequest) { const searchParams = request.nextUrl.searchParams; const detailed = searchParams.get('detailed') === 'true'; @@ -43,9 +32,9 @@ export async function GET(request: NextRequest) { try { const timestamp = new Date().toISOString(); const memUsage = process.memoryUsage(); - const heapUsagePercent = (memUsage.heapUsed / memUsage.heapTotal) * 100; + const heapLimit = getHeapStatistics().heap_size_limit; + const heapUsagePercent = (memUsage.heapUsed / heapLimit) * 100; - // Determine health status based on memory usage let status: 'healthy' | 'degraded' | 'unhealthy' = 'healthy'; const warnings: string[] = []; let httpStatus = 200; @@ -58,7 +47,6 @@ export async function GET(request: NextRequest) { warnings.push(`Memory usage high: ${heapUsagePercent.toFixed(1)}%`); } - // Build response const response: HealthStatus = { status, timestamp, @@ -68,13 +56,13 @@ export async function GET(request: NextRequest) { response.reason = `Memory usage critical: ${heapUsagePercent.toFixed(1)}%`; } - // Add detailed information if requested if (detailed) { response.uptime = process.uptime(); response.version = process.env.npm_package_version || '0.1.0'; response.memory = { heapUsed: memUsage.heapUsed, heapTotal: memUsage.heapTotal, + heapLimit, rss: memUsage.rss, external: memUsage.external, heapUsagePercent: Number(heapUsagePercent.toFixed(2)), @@ -111,13 +99,11 @@ export async function GET(request: NextRequest) { } } -/** - * HEAD method for ultra-lightweight health checks - */ export async function HEAD() { try { const memUsage = process.memoryUsage(); - const heapUsagePercent = (memUsage.heapUsed / memUsage.heapTotal) * 100; + const heapLimit = getHeapStatistics().heap_size_limit; + const heapUsagePercent = (memUsage.heapUsed / heapLimit) * 100; if (heapUsagePercent >= MEMORY_CRITICAL_THRESHOLD * 100) { return new Response(null, { status: 503 }); diff --git a/app/global-error.tsx b/app/global-error.tsx index 5e38984..e156ab9 100644 --- a/app/global-error.tsx +++ b/app/global-error.tsx @@ -23,7 +23,6 @@ export default function GlobalError({ reset: () => void; }) { useEffect(() => { - console.error("Global error:", error); }, [error]); return ( diff --git a/components/contacts/contact-import-dialog.tsx b/components/contacts/contact-import-dialog.tsx index 372e1b4..f8bd86f 100644 --- a/components/contacts/contact-import-dialog.tsx +++ b/components/contacts/contact-import-dialog.tsx @@ -59,8 +59,7 @@ export function ContactImportDialog({ if (!dupes.has(idx)) initialSelected.add(idx); }); setSelected(initialSelected); - } catch (error) { - console.error('Failed to parse vCard:', error); + } catch { setError(t("import.parse_error")); } }, [existingContacts, t]); @@ -91,8 +90,7 @@ export function ContactImportDialog({ try { const count = await onImport(toImport); setResult(count); - } catch (error) { - console.error('Failed to import contacts:', error); + } catch { setError(t("import.failed")); } finally { setIsImporting(false); diff --git a/components/email/calendar-invitation-banner.tsx b/components/email/calendar-invitation-banner.tsx index 5fa57f5..e3da52f 100644 --- a/components/email/calendar-invitation-banner.tsx +++ b/components/email/calendar-invitation-banner.tsx @@ -87,7 +87,7 @@ export function CalendarInvitationBanner({ email }: CalendarInvitationBannerProp if (attachment) { parseEvent(); } - }, [email.id]); // eslint-disable-line react-hooks/exhaustive-deps + }, [email.id]); // eslint-disable-line react-hooks/exhaustive-deps -- only re-parse when viewing a different email useEffect(() => { if (calendars.length > 0 && !selectedCalendarId) { diff --git a/components/email/email-composer.tsx b/components/email/email-composer.tsx index a72ba70..7fe2584 100644 --- a/components/email/email-composer.tsx +++ b/components/email/email-composer.tsx @@ -381,8 +381,7 @@ export function EmailComposer({ setTimeout(() => setSaveStatus('idle'), 2000); return savedDraftId; - } catch (error) { - console.error('Failed to save draft:', error); + } catch { setSaveStatus('error'); setTimeout(() => setSaveStatus('idle'), 3000); return null; diff --git a/components/email/email-list.tsx b/components/email/email-list.tsx index 7fb1cc9..081853a 100644 --- a/components/email/email-list.tsx +++ b/components/email/email-list.tsx @@ -4,7 +4,7 @@ import { Email, ThreadGroup } from "@/lib/jmap/types"; import { ThreadListItem } from "./thread-list-item"; import { EmailContextMenu } from "./email-context-menu"; import { cn } from "@/lib/utils"; -import { Inbox, CheckSquare, Square, Trash2, Mail, MailOpen, Loader2 } from "lucide-react"; +import { Inbox, Trash2, Mail, MailOpen, Loader2 } from "lucide-react"; import { useState, useEffect, useRef, useCallback, useMemo } from "react"; import { Button } from "@/components/ui/button"; import { ConfirmDialog } from "@/components/ui/confirm-dialog"; @@ -18,6 +18,8 @@ import { useTranslations } from "next-intl"; import { useVirtualizer } from "@tanstack/react-virtual"; import { SearchChips } from "@/components/search/search-chips"; import { isFilterEmpty, DEFAULT_SEARCH_FILTERS } from "@/lib/jmap/search-utils"; +import { SelectionDropdown } from "./selection-dropdown"; +import { MoveToPopover } from "./move-to-popover"; interface EmailListProps { emails: Email[]; @@ -62,8 +64,12 @@ export function EmailList({ const { client } = useAuthStore(); const { selectedEmailIds, + toggleEmailSelection, selectAllEmails, clearSelection, + selectRange, + lastSelectedIndex, + selectByFilter, batchMarkAsRead, batchDelete, batchMoveToMailbox, @@ -86,6 +92,16 @@ export function EmailList({ advancedSearch, } = useEmailStore(); + const [showRefreshOverlay, setShowRefreshOverlay] = useState(false); + useEffect(() => { + if (!isLoading || emails.length === 0) { + setShowRefreshOverlay(false); + return; + } + const timer = setTimeout(() => setShowRefreshOverlay(true), 300); + return () => clearTimeout(timer); + }, [isLoading, emails.length]); + const threadGroups = useMemo(() => { const groups = groupEmailsByThread(emails); return sortThreadGroups(groups); @@ -133,7 +149,7 @@ export function EmailList({ ); const hasSelection = selectedEmailIds.size > 0; - const allSelected = emails.length > 0 && emails.every(e => selectedEmailIds.has(e.id)); + const allSelected = threadGroups.length > 0 && threadGroups.every(g => selectedEmailIds.has(g.latestEmail.id)); const handleBatchMarkAsRead = async (read: boolean) => { if (!client || isProcessing) return; @@ -164,6 +180,25 @@ export function EmailList({ } }; + const handleBatchMove = async (mailboxId: string) => { + if (!client || isProcessing) return; + setIsProcessing(true); + try { + await batchMoveToMailbox(client, mailboxId); + const { toast } = await import('sonner'); + const targetMailbox = mailboxes.find(m => m.id === mailboxId); + toast.success(t('batch_actions.move_success', { + count: selectedEmailIds.size, + folder: targetMailbox?.name || mailboxId, + })); + } catch { + const { toast } = await import('sonner'); + toast.error(t('batch_actions.move_error')); + } finally { + setTimeout(() => setIsProcessing(false), 500); + } + }; + const handleLoadMore = useCallback(() => { if (client && hasMoreEmails && !isLoadingMore && !isLoading) { loadMoreEmails(client); @@ -181,6 +216,15 @@ export function EmailList({ } }, [client, expandedThreadIds, toggleThreadExpansion, fetchThreadEmails]); + const handleCheckboxClick = useCallback((e: React.MouseEvent, emailId: string, groupIndex: number) => { + e.stopPropagation(); + if (e.shiftKey && lastSelectedIndex !== null) { + selectRange(lastSelectedIndex, groupIndex, threadGroups); + } else { + toggleEmailSelection(emailId, groupIndex); + } + }, [lastSelectedIndex, selectRange, toggleEmailSelection, threadGroups]); + // Range-based load more: trigger when last visible item is near the end const virtualItems = virtualizer.getVirtualItems(); const lastVirtualItemIndex = virtualItems[virtualItems.length - 1]?.index; @@ -202,15 +246,36 @@ export function EmailList({ if (index >= 0) { virtualizer.scrollToIndex(index, { align: 'auto' }); } - // eslint-disable-next-line react-hooks/exhaustive-deps + // eslint-disable-next-line react-hooks/exhaustive-deps -- scroll only on selection change, not on list re-renders }, [selectedEmailId]); // Re-measure all items when density or preview settings change useEffect(() => { virtualizer.measure(); - // eslint-disable-next-line react-hooks/exhaustive-deps + // eslint-disable-next-line react-hooks/exhaustive-deps -- virtualizer ref is stable, only re-measure on setting changes }, [listDensity, showPreview]); + useEffect(() => { + const container = parentRef.current; + if (!container) return; + + const handleKeyDown = (e: KeyboardEvent) => { + if ((e.ctrlKey || e.metaKey) && e.key === 'a') { + const activeEl = document.activeElement; + const isInInput = activeEl instanceof HTMLInputElement || + activeEl instanceof HTMLTextAreaElement || + (activeEl instanceof HTMLElement && activeEl.isContentEditable); + if (isInInput) return; + + e.preventDefault(); + selectAllEmails(threadGroups); + } + }; + + container.addEventListener('keydown', handleKeyDown); + return () => container.removeEventListener('keydown', handleKeyDown); + }, [selectAllEmails, threadGroups]); + return (
{/* Batch Actions Toolbar */} @@ -222,8 +287,8 @@ export function EmailList({ >
- - {selectedEmailIds.size} {selectedEmailIds.size === 1 ? 'email' : 'emails'} selected + + {t('batch_actions.selected_count', { count: selectedEmailIds.size })}
@@ -255,6 +320,12 @@ export function EmailList({ )} +
@@ -303,22 +374,12 @@ export function EmailList({ {/* List Header */}
- + selectByFilter(filter, groups)} + threadGroups={threadGroups} + />

{isLoading ? t('loading') : threadGroups.length > 0 ? (totalEmails !== undefined && totalEmails > threadGroups.length @@ -332,9 +393,9 @@ export function EmailList({

{/* Email List */} -
+
{/* Loading overlay */} - {isLoading && emails.length > 0 && ( + {showRefreshOverlay && (
@@ -354,7 +415,7 @@ export function EmailList({ ) : ( <>
onEmailSelect?.(email)} onContextMenu={openContextMenu} onOpenConversation={onOpenConversation} + isChecked={selectedEmailIds.has(thread.latestEmail.id)} + onCheckboxClick={(e) => handleCheckboxClick(e, thread.latestEmail.id, virtualItem.index)} />
); diff --git a/components/email/email-viewer.tsx b/components/email/email-viewer.tsx index c60ac4b..2ddba94 100644 --- a/components/email/email-viewer.tsx +++ b/components/email/email-viewer.tsx @@ -186,7 +186,6 @@ export function EmailViewer({ className, }: EmailViewerProps) { const t = useTranslations('email_viewer'); - const tNotifications = useTranslations('notifications'); const tCommon = useTranslations('common'); const externalContentPolicy = useSettingsStore((state) => state.externalContentPolicy); const addTrustedSender = useSettingsStore((state) => state.addTrustedSender); @@ -209,7 +208,7 @@ export function EmailViewer({ // Tablet list visibility const { isMobile, isTablet } = useDeviceDetection(); const { tabletListVisible } = useUIStore(); - const { identities } = useAuthStore(); + const { identities, client } = useAuthStore(); const resolvedTheme = useThemeStore((state) => state.resolvedTheme); const [showFullHeaders, setShowFullHeaders] = useState(false); const [allowExternalContent, setAllowExternalContent] = useState(false); @@ -229,6 +228,43 @@ export function EmailViewer({ } ); + const [cidUrls, setCidUrls] = useState>(new Map()); + useEffect(() => { + if (!email?.attachments || !client) { + setCidUrls(new Map()); + return; + } + const inlineAtts = email.attachments.filter(a => a.cid && a.blobId); + if (inlineAtts.length === 0) { + setCidUrls(new Map()); + return; + } + let cancelled = false; + const objectUrls: string[] = []; + Promise.all( + inlineAtts.map(async (att) => { + try { + const objectUrl = await client.fetchBlobAsObjectUrl(att.blobId, att.name, att.type); + objectUrls.push(objectUrl); + return [att.cid!, objectUrl] as const; + } catch { + return null; + } + }) + ).then((results) => { + if (cancelled) return; + const map = new Map(); + for (const r of results) { + if (r) map.set(r[0], r[1]); + } + setCidUrls(map); + }); + return () => { + cancelled = true; + objectUrls.forEach(url => URL.revokeObjectURL(url)); + }; + }, [email?.id, email?.attachments, client]); + useEffect(() => { // Mark as read when email is viewed if (email && !email.keywords?.$seen && onMarkAsRead) { @@ -389,16 +425,17 @@ export function EmailViewer({ return source; }; + const [sourceCopied, setSourceCopied] = useState(false); const copySourceToClipboard = async () => { if (!email) return; try { const source = generateEmailSource(email); await navigator.clipboard.writeText(source); - // Could add a toast notification here - console.log(tNotifications('source_copied')); - } catch (err) { - console.error('Failed to copy source:', err); + setSourceCopied(true); + setTimeout(() => setSourceCopied(false), 2000); + } catch { + return; } }; @@ -512,6 +549,14 @@ export function EmailViewer({ setHasBlockedContent(true); } + // Replace cid: references with pre-fetched object URLs + if (cidUrls.size > 0) { + cleanHtml = cleanHtml.replace(/src="cid:([^"]+)"/gi, (match, cid) => { + const url = cidUrls.get(cid); + return url ? `src="${url}"` : match; + }); + } + return { html: cleanHtml, isHtml: true, @@ -561,7 +606,7 @@ export function EmailViewer({ html: '

No content available

', isHtml: false }; - }, [email, allowExternalContent, hasBlockedContent, externalContentPolicy, isSenderTrusted, resolvedTheme]); + }, [email, allowExternalContent, hasBlockedContent, externalContentPolicy, isSenderTrusted, resolvedTheme, cidUrls]); // Detect List-Unsubscribe header for newsletter banners const listHeaders = useMemo(() => { @@ -1179,7 +1224,7 @@ export function EmailViewer({
)} - {/* AI Analysis (X-Spam-LLM) - Full width card */} + {/* Spam analysis (X-Spam-LLM header) */} {email.spamLLM && (
- AI Analysis: {email.spamLLM.verdict} + Spam Analysis: {email.spamLLM.verdict}

@@ -1457,20 +1502,22 @@ export function EmailViewer({

- {/* Inline Attachments */} - {email.attachments && email.attachments.length > 0 && ( + {/* Attachments (excluding inline/CID images already rendered in the body) */} + {email.attachments && email.attachments.filter(a => !a.cid).length > 0 && (
{/* Image attachments as thumbnails */} {email.attachments.filter(a => + !a.cid && ( a.type?.startsWith('image/') || - ['jpg', 'jpeg', 'png', 'gif', 'webp'].includes(a.name?.split('.').pop()?.toLowerCase() || '') + ['jpg', 'jpeg', 'png', 'gif', 'webp'].includes(a.name?.split('.').pop()?.toLowerCase() || '')) ).length > 0 && (
{email.attachments .filter(a => + !a.cid && ( a.type?.startsWith('image/') || - ['jpg', 'jpeg', 'png', 'gif', 'webp'].includes(a.name?.split('.').pop()?.toLowerCase() || '') + ['jpg', 'jpeg', 'png', 'gif', 'webp'].includes(a.name?.split('.').pop()?.toLowerCase() || '')) ) .map((attachment, i) => (
+ !a.cid && !a.type?.startsWith('image/') && !['jpg', 'jpeg', 'png', 'gif', 'webp'].includes(a.name?.split('.').pop()?.toLowerCase() || '') ).length > 0 && ( @@ -1509,6 +1557,7 @@ export function EmailViewer({ {email.attachments .filter(a => + !a.cid && !a.type?.startsWith('image/') && !['jpg', 'jpeg', 'png', 'gif', 'webp'].includes(a.name?.split('.').pop()?.toLowerCase() || '') ) @@ -1639,8 +1688,8 @@ export function EmailViewer({ await onQuickReply(quickReplyText); setQuickReplyText(""); setIsQuickReplyFocused(false); - } catch (error) { - console.error("Failed to send quick reply:", error); + } catch { + return; } finally { setIsSendingQuickReply(false); } @@ -1706,8 +1755,8 @@ export function EmailViewer({ onClick={copySourceToClipboard} className="flex items-center gap-1.5" > - - {t('copy_source')} + {sourceCopied ? : } + {sourceCopied ? tCommon('copied') : t('copy_source')} + + {isOpen && createPortal( +
+
+
+ + setSearch(e.target.value)} + placeholder={t('move_to.search_placeholder')} + className={cn( + "w-full pl-8 pr-3 py-1.5 text-sm rounded-md", + "bg-muted/50 border border-border", + "focus:outline-none focus:ring-2 focus:ring-primary/30 focus:border-primary", + "placeholder:text-muted-foreground" + )} + /> +
+
+ +
+ {allFiltered.length === 0 ? ( +
+ {availableMailboxes.length === 0 + ? t('move_to.no_other_folders') + : t('move_to.no_results')} +
+ ) : ( + <> + {filteredMailboxes.system.map((mailbox, index) => { + const Icon = getMailboxIcon(mailbox); + return ( + + ); + })} + + {filteredMailboxes.custom.length > 0 && ( + <> + {filteredMailboxes.system.length > 0 && ( +
+ {t('move_to.custom_folders')} +
+ )} + {filteredMailboxes.custom.map((mailbox, index) => { + const Icon = getMailboxIcon(mailbox); + const depth = getMailboxDepth(mailbox, mailboxes); + const flatIndex = filteredMailboxes.system.length + index; + return ( + + ); + })} + + )} + + )} +
+
, + document.body + )} + + ); +} diff --git a/components/email/selection-dropdown.tsx b/components/email/selection-dropdown.tsx new file mode 100644 index 0000000..56f5318 --- /dev/null +++ b/components/email/selection-dropdown.tsx @@ -0,0 +1,124 @@ +"use client"; + +import { useState, useRef, useEffect } from "react"; +import { createPortal } from "react-dom"; +import { ChevronDown } from "lucide-react"; +import { cn } from "@/lib/utils"; +import { useTranslations } from "next-intl"; +import { ThreadGroup } from "@/lib/jmap/types"; + +type SelectionFilter = 'all' | 'none' | 'read' | 'unread' | 'starred' | 'unstarred'; + +interface SelectionDropdownProps { + hasSelection: boolean; + allSelected: boolean; + onSelectByFilter: (filter: SelectionFilter, threadGroups: ThreadGroup[]) => void; + threadGroups: ThreadGroup[]; +} + +export function SelectionDropdown({ hasSelection, allSelected, onSelectByFilter, threadGroups }: SelectionDropdownProps) { + const t = useTranslations('email_list'); + const [isOpen, setIsOpen] = useState(false); + const buttonRef = useRef(null); + const menuRef = useRef(null); + + useEffect(() => { + if (!isOpen) return; + const handleClickOutside = (e: MouseEvent) => { + if ( + menuRef.current && !menuRef.current.contains(e.target as Node) && + buttonRef.current && !buttonRef.current.contains(e.target as Node) + ) { + setIsOpen(false); + } + }; + const handleEscape = (e: KeyboardEvent) => { + if (e.key === 'Escape') setIsOpen(false); + }; + document.addEventListener('mousedown', handleClickOutside); + document.addEventListener('keydown', handleEscape); + return () => { + document.removeEventListener('mousedown', handleClickOutside); + document.removeEventListener('keydown', handleEscape); + }; + }, [isOpen]); + + const handleSelect = (filter: SelectionFilter) => { + onSelectByFilter(filter, threadGroups); + setIsOpen(false); + }; + + const filters: { key: SelectionFilter; label: string }[] = [ + { key: 'all', label: t('select_criteria.all') }, + { key: 'none', label: t('select_criteria.none') }, + { key: 'read', label: t('select_criteria.read') }, + { key: 'unread', label: t('select_criteria.unread') }, + { key: 'starred', label: t('select_criteria.starred') }, + { key: 'unstarred', label: t('select_criteria.unstarred') }, + ]; + + const getMenuPosition = () => { + if (!buttonRef.current) return { top: 0, left: 0 }; + const rect = buttonRef.current.getBoundingClientRect(); + return { top: rect.bottom + 4, left: rect.left }; + }; + + return ( +
+ + + {isOpen && createPortal( +
+
+ {filters.map(({ key, label }) => ( + + ))} +
+
, + document.body + )} +
+ ); +} diff --git a/components/email/thread-conversation-view.tsx b/components/email/thread-conversation-view.tsx index 0ba93fd..11f7f21 100644 --- a/components/email/thread-conversation-view.tsx +++ b/components/email/thread-conversation-view.tsx @@ -30,6 +30,7 @@ import { } from "lucide-react"; import { useTranslations } from "next-intl"; import { useSettingsStore } from "@/stores/settings-store"; +import { useAuthStore } from "@/stores/auth-store"; interface ThreadConversationViewProps { thread: ThreadGroup; @@ -225,6 +226,44 @@ function EmailCard({ const isUnread = !email.keywords?.$seen; const isStarred = email.keywords?.$flagged; const [hasBlockedContent, setHasBlockedContent] = useState(false); + const { client } = useAuthStore(); + + const [cidUrls, setCidUrls] = useState>(new Map()); + useEffect(() => { + if (!email?.attachments || !client) { + setCidUrls(new Map()); + return; + } + const inlineAtts = email.attachments.filter(a => a.cid && a.blobId); + if (inlineAtts.length === 0) { + setCidUrls(new Map()); + return; + } + let cancelled = false; + const objectUrls: string[] = []; + Promise.all( + inlineAtts.map(async (att) => { + try { + const objectUrl = await client.fetchBlobAsObjectUrl(att.blobId, att.name, att.type); + objectUrls.push(objectUrl); + return [att.cid!, objectUrl] as const; + } catch { + return null; + } + }) + ).then((results) => { + if (cancelled) return; + const map = new Map(); + for (const r of results) { + if (r) map.set(r[0], r[1]); + } + setCidUrls(map); + }); + return () => { + cancelled = true; + objectUrls.forEach(url => URL.revokeObjectURL(url)); + }; + }, [email?.id, email?.attachments, client]); // Mark as read when email is expanded useEffect(() => { @@ -333,6 +372,14 @@ function EmailCard({ finalHtml = collapseBlockedImageContainers(sanitized); } + // Replace cid: references with pre-fetched object URLs + if (cidUrls.size > 0) { + finalHtml = finalHtml.replace(/src="cid:([^"]+)"/gi, (match, cid) => { + const url = cidUrls.get(cid); + return url ? `src="${url}"` : match; + }); + } + return { html: finalHtml, isHtml: true, useIframe: needsIframeRendering(htmlContent) }; } @@ -355,7 +402,7 @@ function EmailCard({ } return { html: "", isHtml: false }; - }, [email, allowExternal, resolvedTheme]); + }, [email, allowExternal, resolvedTheme, cidUrls]); return (
- {/* Attachments */} - {email.attachments && email.attachments.length > 0 && ( + {/* Attachments (excluding inline/CID images rendered in the body) */} + {email.attachments && email.attachments.filter(a => !a.cid).length > 0 && (
- {email.attachments.map((attachment, idx) => { + {email.attachments.filter(a => !a.cid).map((attachment, idx) => { const Icon = getFileIcon(attachment.name, attachment.type); return ( {isUnread && (
@@ -158,6 +177,8 @@ export const ThreadListItem = React.forwardRef state.showPreview); @@ -180,6 +201,8 @@ export const ThreadListItem = React.forwardRef ); } @@ -221,7 +244,8 @@ export const ThreadListItem = React.forwardRef + {!isMobile && ( + + )} + {!isMobile && ( + +
+
{hintText}
+
+ ); +} + +function RenameInput({ defaultValue, onSubmit, onCancel }: { + defaultValue: string; + onSubmit: (value: string) => void; + onCancel: () => void; +}) { + const cancelledRef = useRef(false); + return ( + { + if (e.key === 'Enter') { + e.preventDefault(); + onSubmit((e.target as HTMLInputElement).value); + } else if (e.key === 'Escape') { + e.preventDefault(); + cancelledRef.current = true; + onCancel(); + } + }} + onBlur={(e) => { + if (!cancelledRef.current) onSubmit(e.target.value); + }} + onClick={(e) => e.stopPropagation()} + className="flex-1 bg-background text-foreground text-sm px-1.5 py-0.5 rounded border-2 border-primary outline-none min-w-0" + /> + ); +} + function MailboxTreeItem({ node, selectedMailbox, @@ -92,6 +204,13 @@ function MailboxTreeItem({ onToggleExpand, onMailboxContextMenu, isCollapsed, + renamingMailboxId, + onRenameSubmit, + onRenameCancel, + onStartRename, + creatingSubfolder, + onCreateSubmit, + onCreateCancel, }: { node: MailboxNode; selectedMailbox: string; @@ -100,16 +219,26 @@ function MailboxTreeItem({ onToggleExpand: (id: string) => void; onMailboxContextMenu?: (e: React.MouseEvent, mailbox: Mailbox) => void; isCollapsed: boolean; + renamingMailboxId: string | null; + onRenameSubmit: (value: string) => void; + onRenameCancel: () => void; + onStartRename: (id: string) => void; + creatingSubfolder: { parentId: string } | null; + onCreateSubmit: (value: string) => void; + onCreateCancel: () => void; }) { const t = useTranslations('sidebar'); + const tFolder = useTranslations('sidebar.folder_management'); const tNotifications = useTranslations('notifications'); const hasChildren = node.children.length > 0; const isExpanded = expandedFolders.has(node.id); const Icon = getIconForMailbox(node.role, node.name, hasChildren, isExpanded, node.isShared, node.id); const indentPixels = node.depth * 16; const isVirtualNode = node.id.startsWith('shared-'); + const isRenaming = renamingMailboxId === node.id; + const isCreatingChild = creatingSubfolder?.parentId === node.id; - const { isDragging: globalDragging } = useDragDropContext(); + const { isDragging: globalDragging, startMailboxDrag, endDrag: globalEndDrag, dragType, draggedMailboxId } = useDragDropContext(); const { dropHandlers, isValidDropTarget, isInvalidDropTarget } = useMailboxDrop({ mailbox: node, onSuccess: (count, mailboxName) => { @@ -130,11 +259,44 @@ function MailboxTreeItem({ }, }); + const canDrag = !isVirtualNode && !node.isShared && !node.id.startsWith('shared-') && + !node.id.startsWith('temp-') && node.myRights?.mayRename; + + const handleFolderDragStart = useCallback((e: React.DragEvent) => { + if (!canDrag) { + e.preventDefault(); + return; + } + e.dataTransfer.effectAllowed = "move"; + e.dataTransfer.setData("application/x-mailbox-id", node.id); + e.dataTransfer.setData("text/plain", node.name); + + const preview = document.createElement("div"); + preview.style.cssText = ` + position: fixed; top: -9999px; left: 0; padding: 6px 12px; + background-color: var(--color-primary, #3b82f6); color: white; + border-radius: 6px; font-size: 13px; font-weight: 500; white-space: nowrap; + `; + preview.textContent = node.name; + document.body.appendChild(preview); + e.dataTransfer.setDragImage(preview, 0, 0); + requestAnimationFrame(() => preview.remove()); + + startMailboxDrag(node.id); + }, [canDrag, node.id, node.name, startMailboxDrag]); + + const handleFolderDragEnd = useCallback(() => { + globalEndDrag(); + }, [globalEndDrag]); + return ( <>
onMailboxContextMenu?.(e, node)} + draggable={canDrag} + onDragStart={handleFolderDragStart} + onDragEnd={handleFolderDragEnd} className={cn( "group w-full flex items-center px-2 py-1 lg:py-1 max-lg:py-3 max-lg:min-h-[44px] text-sm transition-all duration-200", isVirtualNode @@ -144,7 +306,8 @@ function MailboxTreeItem({ : "hover:bg-muted text-foreground", node.depth === 0 && !isVirtualNode && "font-medium", isValidDropTarget && "bg-primary/20 ring-2 ring-primary ring-inset", - isInvalidDropTarget && "bg-destructive/10 ring-2 ring-destructive/30 ring-inset opacity-50" + isInvalidDropTarget && "bg-destructive/10 ring-2 ring-destructive/30 ring-inset opacity-50", + globalDragging && dragType === 'mailbox' && draggedMailboxId === node.id && "opacity-40" )} > {hasChildren && ( @@ -169,8 +332,8 @@ function MailboxTreeItem({ )}
)} + + {!hasChildren && isCreatingChild && !isCollapsed && ( + + )} ); } @@ -422,6 +629,85 @@ function StorageQuota({ quota, isCollapsed }: { quota: { used: number; total: nu ); } +function MoveToSubmenu({ + mailbox, + allMailboxes, + onMove, +}: { + mailbox: Mailbox; + allMailboxes: Mailbox[]; + onMove: (targetId: string | null) => void; + onClose: () => void; +}) { + const tFolder = useTranslations('sidebar.folder_management'); + const [showSubmenu, setShowSubmenu] = useState(false); + + const isDescendant = (parentId: string, checkId: string): boolean => { + const children = allMailboxes.filter(mb => mb.parentId === parentId); + return children.some(c => c.id === checkId || isDescendant(c.id, checkId)); + }; + + const tree = buildMailboxTree(allMailboxes); + const flatTree = flattenMailboxTree(tree); + const validTargets = flatTree.filter(mb => { + if (mb.id === mailbox.id) return false; + if (mb.isShared || mb.id.startsWith('shared-')) return false; + if (!mb.myRights?.mayCreateChild) return false; + if (isDescendant(mailbox.id, mb.id)) return false; + return true; + }); + + const canMoveToRoot = !!mailbox.parentId; + + return ( +
setShowSubmenu(true)} + onMouseLeave={() => setShowSubmenu(false)} + > +
+ + {tFolder("move_to")} + +
+ + {showSubmenu && ( +
+ {canMoveToRoot && ( + <> + +
+ + )} + {validTargets.length === 0 && !canMoveToRoot ? ( +
+ {tFolder("no_available_targets")} +
+ ) : ( + validTargets.map((target) => ( + + )) + )} +
+ )} +
+ ); +} + export function Sidebar({ mailboxes = [], selectedMailbox = "", @@ -440,10 +726,18 @@ export function Sidebar({ const [searchQuery, setSearchQuery] = useState(""); const [expandedFolders, setExpandedFolders] = useState>(new Set()); const [contextMenu, setContextMenu] = useState<{ x: number; y: number; mailbox: Mailbox } | null>(null); + const contextMenuRef = useRef(null); const [emptyFolderTarget, setEmptyFolderTarget] = useState(null); + const [renamingMailboxId, setRenamingMailboxId] = useState(null); + const [creatingSubfolder, setCreatingSubfolder] = useState<{ parentId: string } | null>(null); + const [creatingTopLevel, setCreatingTopLevel] = useState(false); + const [deleteFolderTarget, setDeleteFolderTarget] = useState(null); + const sidebarRef = useRef(null); const t = useTranslations('sidebar'); + const tFolder = useTranslations('sidebar.folder_management'); + const { dragType, endDrag: globalEndDrag } = useDragDropContext(); const { client } = useAuthStore(); - const { emptyFolder } = useEmailStore(); + const { emptyFolder, createMailbox, renameMailbox, moveMailbox, deleteMailbox } = useEmailStore(); const { sidebarWidth, updateSetting } = useSettingsStore(); const handleSidebarResize = useCallback((width: number) => { @@ -507,12 +801,78 @@ export function Sidebar({ }; const handleMailboxContextMenu = useCallback((e: React.MouseEvent, mailbox: Mailbox) => { - if (mailbox.role !== "trash" && mailbox.role !== "junk") return; - if (!mailbox.totalEmails || mailbox.totalEmails <= 0) return; + if (mailbox.isShared || mailbox.id.startsWith('shared-')) return; e.preventDefault(); setContextMenu({ x: e.clientX, y: e.clientY, mailbox }); }, []); + const handleCreateFolder = useCallback(async (name: string, parentId?: string) => { + const trimmed = name.trim(); + if (!trimmed) { + toast.error(tFolder('folder_name_empty_error')); + return; + } + if (trimmed.includes('/')) { + toast.error(tFolder('folder_name_slash_error')); + return; + } + if (!client) return; + setCreatingSubfolder(null); + setCreatingTopLevel(false); + try { + await createMailbox(client, trimmed, parentId); + toast.success(tFolder('folder_created', { name: trimmed })); + if (parentId) { + setExpandedFolders(prev => { + const next = new Set(prev); + next.add(parentId); + return next; + }); + } + } catch { + toast.error(tFolder('create_error')); + } + }, [client, createMailbox, tFolder]); + + const handleRenameFolder = useCallback(async (newName: string) => { + const trimmed = newName.trim(); + if (!renamingMailboxId || !client) { + setRenamingMailboxId(null); + return; + } + const mailbox = mailboxes.find(mb => mb.id === renamingMailboxId); + if (!trimmed || trimmed === mailbox?.name) { + setRenamingMailboxId(null); + return; + } + if (trimmed.includes('/')) { + toast.error(tFolder('folder_name_slash_error')); + setRenamingMailboxId(null); + return; + } + const targetId = renamingMailboxId; + setRenamingMailboxId(null); + try { + await renameMailbox(client, targetId, trimmed); + toast.success(tFolder('folder_renamed', { name: trimmed })); + } catch { + toast.error(tFolder('rename_error')); + } + }, [renamingMailboxId, client, mailboxes, renameMailbox, tFolder]); + + const handleDeleteFolder = useCallback(async () => { + if (!deleteFolderTarget || !client) return; + const folderName = deleteFolderTarget.name; + const targetId = deleteFolderTarget.id; + setDeleteFolderTarget(null); + try { + await deleteMailbox(client, targetId); + toast.success(tFolder('folder_deleted', { name: folderName })); + } catch { + toast.error(tFolder('delete_error')); + } + }, [deleteFolderTarget, client, deleteMailbox, tFolder]); + const handleEmptyFolder = useCallback(async () => { if (!emptyFolderTarget || !client) return; const folderName = emptyFolderTarget.name; @@ -565,8 +925,23 @@ export function Sidebar({ return () => window.removeEventListener('keydown', handleKeyDown); }, [selectedMailbox, isCollapsed, expandedFolders, mailboxTree]); + useEffect(() => { + const handleF2 = (e: KeyboardEvent) => { + if (e.key !== 'F2' || !selectedMailbox) return; + if (!sidebarRef.current?.contains(document.activeElement) && document.activeElement !== document.body) return; + const mailbox = mailboxes.find(mb => mb.id === selectedMailbox); + if (!mailbox || mailbox.role || mailbox.isShared || mailbox.id.startsWith('shared-')) return; + e.preventDefault(); + setRenamingMailboxId(selectedMailbox); + }; + + window.addEventListener('keydown', handleF2); + return () => window.removeEventListener('keydown', handleF2); + }, [selectedMailbox, mailboxes]); + return (
{!isCollapsed && ( - + <> + + + )}
@@ -642,7 +1028,32 @@ export function Sidebar({ )} {/* Mailbox List */} -
+
{ + if (dragType === 'mailbox') { + e.preventDefault(); + e.dataTransfer.dropEffect = "move"; + } + }} + onDrop={async (e) => { + if (dragType !== 'mailbox') return; + e.preventDefault(); + const mailboxId = e.dataTransfer.getData("application/x-mailbox-id"); + if (!mailboxId || !client) return; + + const mb = mailboxes.find(m => m.id === mailboxId); + if (mb && mb.parentId) { + try { + await moveMailbox(client, mailboxId, null); + toast.success(tFolder('folder_moved_to_root')); + } catch { + toast.error(tFolder('move_error')); + } + } + globalEndDrag(); + }} + >
{mailboxes.length === 0 ? (
@@ -660,8 +1071,24 @@ export function Sidebar({ onToggleExpand={handleToggleExpand} onMailboxContextMenu={handleMailboxContextMenu} isCollapsed={isCollapsed} + renamingMailboxId={renamingMailboxId} + onRenameSubmit={handleRenameFolder} + onRenameCancel={() => setRenamingMailboxId(null)} + onStartRename={(id) => setRenamingMailboxId(id)} + creatingSubfolder={creatingSubfolder} + onCreateSubmit={(name) => handleCreateFolder(name, creatingSubfolder?.parentId)} + onCreateCancel={() => setCreatingSubfolder(null)} /> ))} + {creatingTopLevel && !isCollapsed && ( + handleCreateFolder(name)} + onCancel={() => setCreatingTopLevel(false)} + depth={0} + /> + )} )}
@@ -670,8 +1097,8 @@ export function Sidebar({
- {/* Mailbox Context Menu */} - {contextMenu && ( + {/* Mailbox Context Menu (portal to escape sidebar overflow) */} + {contextMenu && createPortal( <>
{ e.preventDefault(); setContextMenu(null); }} />
- + {/* New subfolder */} + {contextMenu.mailbox.myRights?.mayCreateChild && ( + + )} + + {/* Rename */} + {!contextMenu.mailbox.role && ( + + )} + + {/* Move to */} + {!contextMenu.mailbox.role && ( + { + if (!client) return; + try { + if (targetId === null) { + await moveMailbox(client, contextMenu.mailbox.id, null); + toast.success(tFolder('folder_moved_to_root')); + } else { + const target = mailboxes.find(mb => mb.id === targetId); + await moveMailbox(client, contextMenu.mailbox.id, targetId); + toast.success(tFolder('folder_moved', { destination: target?.name || '' })); + } + } catch { + toast.error(tFolder('move_error')); + } + setContextMenu(null); + }} + onClose={() => setContextMenu(null)} + /> + )} + + {/* Empty folder (trash/junk only) */} + {(contextMenu.mailbox.role === "trash" || contextMenu.mailbox.role === "junk") && + contextMenu.mailbox.totalEmails && contextMenu.mailbox.totalEmails > 0 && ( + + )} + + {/* Delete folder */} + {!contextMenu.mailbox.role && ( + <> +
+ + + )}
- + , + document.body )} {/* Empty Folder Confirmation Dialog */} @@ -708,6 +1219,34 @@ export function Sidebar({ /> )} + {/* Delete Folder Confirmation Dialog (portal to escape sidebar overflow) */} + {deleteFolderTarget && createPortal((() => { + const descendantCount = mailboxes.filter(mb => { + const isDesc = (parentId: string, checkId: string): boolean => { + if (parentId === checkId) return true; + const children = mailboxes.filter(m => m.parentId === parentId); + return children.some(c => isDesc(c.id, checkId)); + }; + return mb.id !== deleteFolderTarget.id && isDesc(deleteFolderTarget.id, mb.id); + }).length; + const emailCount = deleteFolderTarget.totalEmails || 0; + const message = emailCount > 0 || descendantCount > 0 + ? tFolder('delete_confirm_with_contents', { emails: emailCount, subfolders: descendantCount }) + : tFolder('delete_confirm_empty'); + + return ( + setDeleteFolderTarget(null)} + onConfirm={handleDeleteFolder} + title={tFolder('delete_confirm_title', { name: deleteFolderTarget.name })} + message={message} + confirmText={tFolder('delete_folder')} + variant="destructive" + /> + ); + })(), document.body)} + {/* Footer: Storage Quota + Sign Out + Push Status */}
diff --git a/components/providers/intl-provider.tsx b/components/providers/intl-provider.tsx index 9454e24..95243f1 100644 --- a/components/providers/intl-provider.tsx +++ b/components/providers/intl-provider.tsx @@ -41,9 +41,8 @@ export function IntlProvider({ locale: initialLocale, children }: IntlProviderPr try { const detectedTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone; setTimeZone(detectedTimeZone); - } catch (error) { + } catch { // Fallback to UTC if detection fails - console.warn('Failed to detect timezone, using UTC:', error); setTimeZone('UTC'); } }, []); @@ -53,7 +52,7 @@ export function IntlProvider({ locale: initialLocale, children }: IntlProviderPr if (!currentLocale) { setLocale(initialLocale); } - // eslint-disable-next-line react-hooks/exhaustive-deps + // eslint-disable-next-line react-hooks/exhaustive-deps -- sync initial locale to store once on mount }, []); // Switch locale immediately when store changes diff --git a/components/settings/vacation-settings.tsx b/components/settings/vacation-settings.tsx index 44761fb..751ff5f 100644 --- a/components/settings/vacation-settings.tsx +++ b/components/settings/vacation-settings.tsx @@ -103,8 +103,7 @@ export function VacationSettings() { textBody: localTextBody, }); toast.success(tNotifications('vacation_saved')); - } catch (error) { - console.error('Failed to save vacation response:', error); + } catch { toast.error(tNotifications('vacation_save_failed')); } }; diff --git a/contexts/drag-drop-context.tsx b/contexts/drag-drop-context.tsx index 97c498e..420be46 100644 --- a/contexts/drag-drop-context.tsx +++ b/contexts/drag-drop-context.tsx @@ -3,23 +3,30 @@ import { createContext, useContext, useState, useCallback, ReactNode } from "react"; import { Email } from "@/lib/jmap/types"; +type DragType = 'email' | 'mailbox'; + interface DragDropState { isDragging: boolean; + dragType: DragType | null; draggedEmails: Email[]; dragCount: number; sourceMailboxId: string | null; + draggedMailboxId: string | null; } interface DragDropContextValue extends DragDropState { - startDrag: (emails: Email[], sourceMailboxId: string) => void; + startDrag: (emails: Email[], sourceMailboxId: string, type?: DragType) => void; + startMailboxDrag: (mailboxId: string) => void; endDrag: () => void; } const initialState: DragDropState = { isDragging: false, + dragType: null, draggedEmails: [], dragCount: 0, sourceMailboxId: null, + draggedMailboxId: null, }; const DragDropContext = createContext(null); @@ -27,12 +34,25 @@ const DragDropContext = createContext(null); export function DragDropProvider({ children }: { children: ReactNode }) { const [state, setState] = useState(initialState); - const startDrag = useCallback((emails: Email[], sourceMailboxId: string) => { + const startDrag = useCallback((emails: Email[], sourceMailboxId: string, type: DragType = 'email') => { setState({ isDragging: true, + dragType: type, draggedEmails: emails, dragCount: emails.length, sourceMailboxId, + draggedMailboxId: null, + }); + }, []); + + const startMailboxDrag = useCallback((mailboxId: string) => { + setState({ + isDragging: true, + dragType: 'mailbox', + draggedEmails: [], + dragCount: 0, + sourceMailboxId: null, + draggedMailboxId: mailboxId, }); }, []); @@ -41,7 +61,7 @@ export function DragDropProvider({ children }: { children: ReactNode }) { }, []); return ( - + {children} ); diff --git a/hooks/use-calendar-alerts.ts b/hooks/use-calendar-alerts.ts index 9dc9c6b..3359a13 100644 --- a/hooks/use-calendar-alerts.ts +++ b/hooks/use-calendar-alerts.ts @@ -103,7 +103,7 @@ export function useCalendarAlerts() { const timer = setTimeout(() => checkAlerts(), 500); return () => clearTimeout(timer); - // eslint-disable-next-line react-hooks/exhaustive-deps + // eslint-disable-next-line react-hooks/exhaustive-deps -- only re-run when auth or notification setting changes, not when callback refs update }, [isAuthenticated, calendarNotificationsEnabled]); useEffect(() => { diff --git a/hooks/use-mailbox-drop.ts b/hooks/use-mailbox-drop.ts index 551e5f6..e542a20 100644 --- a/hooks/use-mailbox-drop.ts +++ b/hooks/use-mailbox-drop.ts @@ -31,35 +31,29 @@ export function useMailboxDrop({ mailbox, onDropComplete, onSuccess, onError }: const [isOver, setIsOver] = useState(false); const { client } = useAuthStore(); const { moveToMailbox, selectedEmailIds, clearSelection, fetchEmails, selectedMailbox } = useEmailStore(); - const { isDragging, sourceMailboxId, draggedEmails, endDrag } = useDragDropContext(); + const { isDragging, sourceMailboxId, draggedEmails, draggedMailboxId, dragType, endDrag } = useDragDropContext(); - // Determine if this is a valid drop target const isValidTarget = useCallback(() => { if (!isDragging) return false; - // Cannot drop on same mailbox - if (mailbox.id === sourceMailboxId) return false; - - // Check if mailbox accepts items - if (!mailbox.myRights?.mayAddItems) return false; - - // Virtual nodes (shared folder headers) cannot be drop targets - if (mailbox.id.startsWith("shared-")) return false; - - // For shared mailboxes, check account compatibility - if (mailbox.isShared && draggedEmails[0]) { - // Get the source mailbox's account ID from the store - const mailboxes = useEmailStore.getState().mailboxes; - const sourceMb = mailboxes.find(mb => mb.id === sourceMailboxId); - - // Cross-account moves are not supported - if (sourceMb?.accountId !== mailbox.accountId) { - return false; - } + if (dragType === 'mailbox') { + if (mailbox.id === draggedMailboxId) return false; + if (mailbox.id.startsWith("shared-") || mailbox.isShared) return false; + if (!mailbox.myRights?.mayCreateChild) return false; + return true; } + // Email drag validation (existing logic) + if (mailbox.id === sourceMailboxId) return false; + if (!mailbox.myRights?.mayAddItems) return false; + if (mailbox.id.startsWith("shared-")) return false; + if (mailbox.isShared && draggedEmails[0]) { + const mailboxes = useEmailStore.getState().mailboxes; + const sourceMb = mailboxes.find(mb => mb.id === sourceMailboxId); + if (sourceMb?.accountId !== mailbox.accountId) return false; + } return true; - }, [isDragging, mailbox, sourceMailboxId, draggedEmails]); + }, [isDragging, mailbox, sourceMailboxId, draggedEmails, draggedMailboxId, dragType]); const handleDragOver = useCallback((e: DragEvent) => { e.preventDefault(); @@ -97,6 +91,38 @@ export function useMailboxDrop({ mailbox, onDropComplete, onSuccess, onError }: return; } + // Handle mailbox drop (reparenting) + const mailboxIdData = e.dataTransfer.getData("application/x-mailbox-id"); + if (mailboxIdData && dragType === 'mailbox') { + try { + const { moveMailbox } = useEmailStore.getState(); + + const allMailboxes = useEmailStore.getState().mailboxes; + const isDescendant = (parentId: string, checkId: string): boolean => { + const children = allMailboxes.filter(mb => mb.parentId === parentId); + return children.some(c => c.id === checkId || isDescendant(c.id, checkId)); + }; + + if (isDescendant(mailboxIdData, mailbox.id)) { + endDrag(); + return; + } + + await moveMailbox(client, mailboxIdData, mailbox.id); + + if (onSuccess) { + onSuccess(1, mailbox.name); + } + } catch (error) { + if (onError) { + onError(error instanceof Error ? error.message : 'Unknown error'); + } + } finally { + endDrag(); + } + return; + } + try { const emailIdsJson = e.dataTransfer.getData("application/x-email-ids"); if (!emailIdsJson) { @@ -136,9 +162,6 @@ export function useMailboxDrop({ mailbox, onDropComplete, onSuccess, onError }: onDropComplete?.(); } catch (error) { - console.error("Failed to move emails:", error); - - // Call error callback if provided, otherwise use fallback if (onError) { onError(error instanceof Error ? error.message : 'Unknown error'); } else { @@ -148,7 +171,7 @@ export function useMailboxDrop({ mailbox, onDropComplete, onSuccess, onError }: } finally { endDrag(); } - }, [client, mailbox, isValidTarget, moveToMailbox, selectedEmailIds, clearSelection, fetchEmails, selectedMailbox, endDrag, onDropComplete, onSuccess, onError]); + }, [client, mailbox, isValidTarget, dragType, moveToMailbox, selectedEmailIds, clearSelection, fetchEmails, selectedMailbox, endDrag, onDropComplete, onSuccess, onError]); const valid = isValidTarget(); diff --git a/lib/__tests__/calendar-participants.test.ts b/lib/__tests__/calendar-participants.test.ts index 0f80ce0..8087dc1 100644 --- a/lib/__tests__/calendar-participants.test.ts +++ b/lib/__tests__/calendar-participants.test.ts @@ -273,26 +273,22 @@ describe('buildParticipantMap', () => { expect(Object.keys(map)).toHaveLength(3); - const org = map['organizer']; + const org = map['mailto:alice@example.com']; expect(org.name).toBe('Alice'); - expect(org.email).toBe('alice@example.com'); - expect(org.roles).toEqual({ owner: true, attendee: true }); + expect(org.calendarAddress).toBe('mailto:alice@example.com'); + expect(org.roles).toEqual({ owner: true }); expect(org.participationStatus).toBe('accepted'); - expect(org.scheduleAgent).toBe('server'); - expect(org.sendTo).toEqual({ imip: 'mailto:alice@example.com' }); - expect(org.expectReply).toBe(false); - const att0 = map['attendee-0']; + const att0 = map['mailto:bob@example.com']; expect(att0.name).toBe('Bob'); - expect(att0.email).toBe('bob@example.com'); + expect(att0.calendarAddress).toBe('mailto:bob@example.com'); expect(att0.roles).toEqual({ attendee: true }); expect(att0.participationStatus).toBe('needs-action'); - expect(att0.scheduleAgent).toBe('server'); expect(att0.expectReply).toBe(true); - const att1 = map['attendee-1']; + const att1 = map['mailto:carol@example.com']; expect(att1.name).toBe('Carol'); - expect(att1.email).toBe('carol@example.com'); + expect(att1.calendarAddress).toBe('mailto:carol@example.com'); }); it('creates only organizer when no attendees', () => { @@ -301,7 +297,7 @@ describe('buildParticipantMap', () => { [] ); expect(Object.keys(map)).toHaveLength(1); - expect(map['organizer']).toBeDefined(); + expect(map['mailto:alice@example.com']).toBeDefined(); }); it('sets @type to Participant for all entries', () => { diff --git a/lib/__tests__/oauth-discovery.test.ts b/lib/__tests__/oauth-discovery.test.ts index 0d2f6d6..35dcec0 100644 --- a/lib/__tests__/oauth-discovery.test.ts +++ b/lib/__tests__/oauth-discovery.test.ts @@ -54,7 +54,6 @@ describe('oauth/discovery', () => { }); it('returns null when both endpoints fail', async () => { - const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); vi.stubGlobal('fetch', vi.fn() .mockResolvedValueOnce({ ok: false, status: 404 }) .mockResolvedValueOnce({ ok: false, status: 404 })); @@ -62,7 +61,6 @@ describe('oauth/discovery', () => { const result = await discoverOAuth('https://fail.example.com'); expect(result).toBeNull(); - expect(consoleSpy).toHaveBeenCalled(); }); it('parses optional fields (revocation_endpoint, end_session_endpoint)', async () => { @@ -78,7 +76,6 @@ describe('oauth/discovery', () => { }); it('returns null when required fields (authorization_endpoint, token_endpoint) are missing', async () => { - const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); vi.stubGlobal('fetch', vi.fn() .mockResolvedValueOnce({ ok: true, @@ -89,7 +86,6 @@ describe('oauth/discovery', () => { const result = await discoverOAuth('https://incomplete.example.com'); expect(result).toBeNull(); - expect(consoleSpy).toHaveBeenCalled(); }); it('caches results — second call for same server URL does not re-fetch', async () => { diff --git a/lib/color-transform.ts b/lib/color-transform.ts index b566088..9bc64f0 100644 --- a/lib/color-transform.ts +++ b/lib/color-transform.ts @@ -155,7 +155,7 @@ export function transformBgColorForDarkMode(colorString: string): string { if (luminance < 0.2) return colorString; const blendFactor = Math.min(0.9, (luminance - 0.2) * 1.125); - const darkR = 30, darkG = 31, darkB = 38; + const darkR = 35, darkG = 35, darkB = 35; const r = Math.max(0, Math.round(rgb.r + (darkR - rgb.r) * blendFactor)); const g = Math.max(0, Math.round(rgb.g + (darkG - rgb.g) * blendFactor)); @@ -244,7 +244,7 @@ export function generateIframeStylesheet(): string { @media (prefers-color-scheme: dark) { body { color: #e2e8f0; - background: #1e1f26; + background: #232323; } a { color: #60a5fa; } img { opacity: 0.9; } diff --git a/lib/email-headers.ts b/lib/email-headers.ts index d652cd2..deb00ef 100644 --- a/lib/email-headers.ts +++ b/lib/email-headers.ts @@ -170,7 +170,7 @@ export function getSecurityStatus(result?: string): { } /** - * Parse X-Spam-LLM header to extract AI verdict and explanation + * Parse X-Spam-LLM header to extract verdict and explanation */ export function parseSpamLLM(header: string): { verdict: string; explanation: string } | null { // Format: "LEGITIMATE (explanation)" or "SPAM (explanation)" diff --git a/lib/jmap/client.ts b/lib/jmap/client.ts index 43ff79e..03124e1 100644 --- a/lib/jmap/client.ts +++ b/lib/jmap/client.ts @@ -49,13 +49,24 @@ interface JMAPEmailHeader { type JMAPMethodCall = [string, Record, string]; -// eslint-disable-next-line @typescript-eslint/no-explicit-any +// eslint-disable-next-line @typescript-eslint/no-explicit-any -- JMAP responses have schema-variable nested payloads type JMAPResponseResult = Record; interface JMAPResponse { methodResponses: Array<[string, JMAPResponseResult, string]>; } +export class JMAPSetError extends Error { + type: string; + description?: string; + constructor(type: string, description?: string) { + super(description || `Mailbox/set error: ${type}`); + this.name = 'JMAPSetError'; + this.type = type; + this.description = description; + } +} + const DEFAULT_MAILBOX_RIGHTS = { mayReadItems: true, mayAddItems: true, @@ -256,12 +267,11 @@ export class JMAPClient { this.pingInterval = setInterval(async () => { try { await this.ping(); - } catch (error) { - console.error('Keep-alive ping failed:', error); + } catch { try { await this.reconnect(); - } catch (reconnectError) { - console.error('Reconnection failed:', reconnectError); + } catch { + return; } } }, 30_000); @@ -347,7 +357,6 @@ export class JMAPClient { const responseText = await response.text(); if (!response.ok) { - console.error('Request failed:', response.status, responseText); throw new Error(`Request failed: ${response.status} - ${responseText.substring(0, 200)}`); } @@ -355,7 +364,6 @@ export class JMAPClient { try { data = JSON.parse(responseText); } catch { - console.error('Failed to parse response:', responseText); throw new Error('Invalid JSON response from server'); } @@ -417,8 +425,7 @@ export class JMAPClient { } throw new Error('Unexpected response format'); - } catch (error) { - console.error('Failed to get mailboxes:', error); + } catch { return [{ id: 'INBOX', originalId: undefined, @@ -481,14 +488,13 @@ export class JMAPClient { allMailboxes.push(...mailboxes); } - } catch (error) { - console.error(`Failed to fetch mailboxes for account ${accountId}:`, error); + } catch { + continue; } } return allMailboxes; - } catch (error) { - console.error("Failed to fetch all mailboxes:", error); + } catch { return this.getMailboxes(); } } @@ -532,8 +538,7 @@ export class JMAPClient { } return { emails: [], hasMore: false, total: 0 }; - } catch (error) { - console.error('Failed to get emails:', error); + } catch { return { emails: [], hasMore: false, total: 0 }; } } @@ -576,8 +581,7 @@ export class JMAPClient { } return email; - } catch (error) { - console.error('Failed to get email:', error); + } catch { return null; } } @@ -769,6 +773,59 @@ export class JMAPClient { ]); } + async createMailbox(name: string, parentId?: string): Promise { + const create: Record = { name }; + if (parentId) create.parentId = parentId; + + const response = await this.request([ + ["Mailbox/set", { + accountId: this.accountId, + create: { "new-mailbox": create }, + }, "0"], + ]); + + const result = response.methodResponses?.[0]?.[1]; + if (result?.notCreated?.["new-mailbox"]) { + const err = result.notCreated["new-mailbox"]; + throw new JMAPSetError(err.type || "unknown", err.description); + } + + const realId = result?.created?.["new-mailbox"]?.id; + if (!realId) throw new JMAPSetError("unknown", "Server did not return created mailbox ID"); + return realId; + } + + async updateMailbox(id: string, changes: { name?: string; parentId?: string | null }): Promise { + const response = await this.request([ + ["Mailbox/set", { + accountId: this.accountId, + update: { [id]: changes }, + }, "0"], + ]); + + const result = response.methodResponses?.[0]?.[1]; + if (result?.notUpdated?.[id]) { + const err = result.notUpdated[id]; + throw new JMAPSetError(err.type || "unknown", err.description); + } + } + + async destroyMailbox(id: string): Promise { + const response = await this.request([ + ["Mailbox/set", { + accountId: this.accountId, + destroy: [id], + onDestroyRemoveEmails: false, + }, "0"], + ]); + + const result = response.methodResponses?.[0]?.[1]; + if (result?.notDestroyed?.[id]) { + const err = result.notDestroyed[id]; + throw new JMAPSetError(err.type || "unknown", err.description); + } + } + async moveEmail(emailId: string, toMailboxId: string, accountId?: string): Promise { const targetAccountId = accountId || this.accountId; const response = await this.request([ @@ -863,8 +920,7 @@ export class JMAPClient { const hasMore = computeHasMore(position, emails.length, total, limit); return { emails, hasMore, total }; - } catch (error) { - console.error('Search failed:', error); + } catch { return { emails: [], hasMore: false, total: 0 }; } } @@ -875,34 +931,29 @@ export class JMAPClient { limit: number = 50, position: number = 0 ): Promise<{ emails: Email[], hasMore: boolean, total: number }> { - try { - const targetAccountId = accountId || this.accountId; + const targetAccountId = accountId || this.accountId; - const response = await this.request([ - ["Email/query", { - accountId: targetAccountId, - filter, - sort: [{ property: "receivedAt", isAscending: false }], - limit, - position, - }, "0"], - ["Email/get", { - accountId: targetAccountId, - "#ids": { resultOf: "0", name: "Email/query", path: "/ids" }, - properties: [...EMAIL_LIST_PROPERTIES], - }, "1"], - ]); + const response = await this.request([ + ["Email/query", { + accountId: targetAccountId, + filter, + sort: [{ property: "receivedAt", isAscending: false }], + limit, + position, + }, "0"], + ["Email/get", { + accountId: targetAccountId, + "#ids": { resultOf: "0", name: "Email/query", path: "/ids" }, + properties: [...EMAIL_LIST_PROPERTIES], + }, "1"], + ]); - const queryResponse = response.methodResponses?.[0]?.[1]; - const emails = response.methodResponses?.[1]?.[1]?.list || []; - const total = queryResponse?.total || 0; - const hasMore = computeHasMore(position, emails.length, total, limit); + const queryResponse = response.methodResponses?.[0]?.[1]; + const emails = response.methodResponses?.[1]?.[1]?.list || []; + const total = queryResponse?.total || 0; + const hasMore = computeHasMore(position, emails.length, total, limit); - return { emails, hasMore, total }; - } catch (error) { - console.error('Advanced search failed:', error); - throw error; - } + return { emails, hasMore, total }; } async getThread(threadId: string, accountId?: string): Promise { @@ -922,8 +973,7 @@ export class JMAPClient { } return null; - } catch (error) { - console.error('Failed to get thread:', error); + } catch { return null; } } @@ -957,27 +1007,29 @@ export class JMAPClient { } return []; - } catch (error) { - console.error('Failed to get thread emails:', error); + } catch { return []; } } + private submissionUsing(): string[] { + return ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail", "urn:ietf:params:jmap:submission"]; + } + async getIdentities(): Promise { try { const response = await this.request([ ["Identity/get", { accountId: this.accountId, }, "0"] - ]); + ], this.submissionUsing()); if (response.methodResponses?.[0]?.[0] === "Identity/get") { return (response.methodResponses[0][1].list || []) as Identity[]; } return []; - } catch (error) { - console.error('Failed to get identities:', error); + } catch { return []; } } @@ -1004,7 +1056,7 @@ export class JMAPClient { } } }, "0"] - ]); + ], this.submissionUsing()); if (response.methodResponses?.[0]?.[0] === "Identity/set") { const result = response.methodResponses[0][1]; @@ -1045,7 +1097,7 @@ export class JMAPClient { [identityId]: updates } }, "0"] - ]); + ], this.submissionUsing()); if (response.methodResponses?.[0]?.[0] === "Identity/set") { const result = response.methodResponses[0][1]; @@ -1072,7 +1124,7 @@ export class JMAPClient { accountId: this.accountId, destroy: [identityId] }, "0"] - ]); + ], this.submissionUsing()); if (response.methodResponses?.[0]?.[0] === "Identity/set") { const result = response.methodResponses[0][1]; @@ -1225,7 +1277,6 @@ export class JMAPClient { if (result.notCreated || result.notUpdated) { const errors = result.notCreated || result.notUpdated; const firstError = Object.values(errors)[0] as { description?: string; type?: string }; - console.error('Draft save error:', firstError); throw new Error(firstError?.description || firstError?.type || 'Failed to save draft'); } @@ -1234,7 +1285,6 @@ export class JMAPClient { } } - console.error('Unexpected draft save response:', response); throw new Error('Failed to save draft'); } @@ -1260,7 +1310,7 @@ export class JMAPClient { if (!finalIdentityId) { const identityResponse = await this.request([ ["Identity/get", { accountId: this.accountId }, "0"] - ]); + ], this.submissionUsing()); finalIdentityId = this.accountId; if (identityResponse.methodResponses?.[0]?.[0] === "Identity/get") { @@ -1312,19 +1362,17 @@ export class JMAPClient { }, "1"]); } - const response = await this.request(methodCalls); + const response = await this.request(methodCalls, this.submissionUsing()); if (response.methodResponses) { for (const [methodName, result] of response.methodResponses) { if (methodName.endsWith('/error')) { - console.error('JMAP method error:', result); throw new Error(result.description || `Failed to send email: ${result.type}`); } if (result.notCreated || result.notUpdated) { const errors = result.notCreated || result.notUpdated; const firstError = Object.values(errors)[0] as { description?: string; type?: string }; - console.error('Email send error:', firstError); throw new Error(firstError?.description || firstError?.type || 'Failed to send email'); } } @@ -1396,6 +1444,16 @@ export class JMAPClient { .replace('{type}', encodeURIComponent(type || 'application/octet-stream')); } + async fetchBlobAsObjectUrl(blobId: string, name?: string, type?: string): Promise { + const url = this.getBlobDownloadUrl(blobId, name, type); + const response = await this.authenticatedFetch(url, {}, { retry: false }); + if (!response.ok) { + throw new Error(`Failed to fetch blob: ${response.status}`); + } + const blob = await response.blob(); + return URL.createObjectURL(blob); + } + getCapabilities(): Record { return this.capabilities; } @@ -1699,8 +1757,7 @@ export class JMAPClient { return (response.methodResponses[0][1].list || []) as AddressBook[]; } return []; - } catch (error) { - console.error('Failed to get address books:', error); + } catch { return []; } } @@ -1725,8 +1782,7 @@ export class JMAPClient { return (response.methodResponses[1][1].list || []) as ContactCard[]; } return []; - } catch (error) { - console.error('Failed to get contacts:', error); + } catch { return []; } } @@ -1746,8 +1802,7 @@ export class JMAPClient { return list[0] || null; } return null; - } catch (error) { - console.error('Failed to get contact:', error); + } catch { return null; } } @@ -1861,8 +1916,7 @@ export class JMAPClient { return (response.methodResponses[1][1].list || []) as ContactCard[]; } return []; - } catch (error) { - console.error('Failed to search contacts:', error); + } catch { return []; } } @@ -1878,8 +1932,7 @@ export class JMAPClient { return (response.methodResponses[0][1].list || []) as Calendar[]; } return []; - } catch (error) { - console.error('Failed to get calendars:', error); + } catch { return []; } } @@ -1985,8 +2038,7 @@ export class JMAPClient { return (response.methodResponses[1][1].list || []) as CalendarEvent[]; } return []; - } catch (error) { - console.error('Failed to get calendar events:', error); + } catch { return []; } } @@ -2021,8 +2073,7 @@ export class JMAPClient { return (response.methodResponses[1][1].list || []) as CalendarEvent[]; } return []; - } catch (error) { - console.error('Failed to query calendar events:', error); + } catch { return []; } } @@ -2043,8 +2094,7 @@ export class JMAPClient { return list[0] || null; } return null; - } catch (error) { - console.error('Failed to get calendar event:', error); + } catch { return null; } } diff --git a/lib/oauth/discovery.ts b/lib/oauth/discovery.ts index 36f869b..53c20b0 100644 --- a/lib/oauth/discovery.ts +++ b/lib/oauth/discovery.ts @@ -48,6 +48,5 @@ export async function discoverOAuth(serverUrl: string): Promise=24.0.0" } }, "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": { - "version": "11.2.6", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", - "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", + "version": "11.2.7", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", + "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", "dev": true, "license": "BlueOak-1.0.0", "engines": { @@ -113,9 +117,9 @@ } }, "node_modules/@asamuzakjp/dom-selector/node_modules/lru-cache": { - "version": "11.2.6", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", - "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", + "version": "11.2.7", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", + "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", "dev": true, "license": "BlueOak-1.0.0", "engines": { @@ -323,23 +327,23 @@ } }, "node_modules/@babel/helpers": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", - "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", "dev": true, "license": "MIT", "dependencies": { "@babel/template": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/types": "^7.29.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", - "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", "dev": true, "license": "MIT", "dependencies": { @@ -385,9 +389,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", - "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", "dev": true, "license": "MIT", "engines": { @@ -552,9 +556,9 @@ } }, "node_modules/@csstools/css-syntax-patches-for-csstree": { - "version": "1.0.28", - "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.28.tgz", - "integrity": "sha512-1NRf1CUBjnr3K7hu8BLxjQrKCxEe8FP/xmPTenAxCRZWVLbmGotkFvG9mfNpjA6k7Bw1bw4BilZq9cu19RA5pg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.1.tgz", + "integrity": "sha512-BvqN0AMWNAnLk9G8jnUT77D+mUbY/H2b3uDTvg2isJkHaOufUE2R3AOwxWo7VBQKT1lOdwdvorddo2B/lk64+w==", "dev": true, "funding": [ { @@ -566,7 +570,15 @@ "url": "https://opencollective.com/csstools" } ], - "license": "MIT-0" + "license": "MIT-0", + "peerDependencies": { + "css-tree": "^3.2.1" + }, + "peerDependenciesMeta": { + "css-tree": { + "optional": true + } + } }, "node_modules/@csstools/css-tokenizer": { "version": "4.0.0", @@ -590,21 +602,21 @@ } }, "node_modules/@emnapi/core": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.8.1.tgz", - "integrity": "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==", + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.1.tgz", + "integrity": "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA==", "dev": true, "license": "MIT", "optional": true, "dependencies": { - "@emnapi/wasi-threads": "1.1.0", + "@emnapi/wasi-threads": "1.2.0", "tslib": "^2.4.0" } }, "node_modules/@emnapi/runtime": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", - "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.1.tgz", + "integrity": "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==", "license": "MIT", "optional": true, "dependencies": { @@ -612,9 +624,9 @@ } }, "node_modules/@emnapi/wasi-threads": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", - "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.0.tgz", + "integrity": "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==", "dev": true, "license": "MIT", "optional": true, @@ -623,9 +635,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", - "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.4.tgz", + "integrity": "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q==", "cpu": [ "ppc64" ], @@ -640,9 +652,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", - "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.4.tgz", + "integrity": "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ==", "cpu": [ "arm" ], @@ -657,9 +669,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", - "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.4.tgz", + "integrity": "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw==", "cpu": [ "arm64" ], @@ -674,9 +686,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", - "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.4.tgz", + "integrity": "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw==", "cpu": [ "x64" ], @@ -691,9 +703,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", - "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.4.tgz", + "integrity": "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ==", "cpu": [ "arm64" ], @@ -708,9 +720,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", - "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.4.tgz", + "integrity": "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw==", "cpu": [ "x64" ], @@ -725,9 +737,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", - "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.4.tgz", + "integrity": "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw==", "cpu": [ "arm64" ], @@ -742,9 +754,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", - "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.4.tgz", + "integrity": "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ==", "cpu": [ "x64" ], @@ -759,9 +771,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", - "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.4.tgz", + "integrity": "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg==", "cpu": [ "arm" ], @@ -776,9 +788,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", - "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.4.tgz", + "integrity": "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA==", "cpu": [ "arm64" ], @@ -793,9 +805,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", - "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.4.tgz", + "integrity": "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA==", "cpu": [ "ia32" ], @@ -810,9 +822,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", - "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.4.tgz", + "integrity": "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA==", "cpu": [ "loong64" ], @@ -827,9 +839,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", - "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.4.tgz", + "integrity": "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw==", "cpu": [ "mips64el" ], @@ -844,9 +856,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", - "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.4.tgz", + "integrity": "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA==", "cpu": [ "ppc64" ], @@ -861,9 +873,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", - "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.4.tgz", + "integrity": "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw==", "cpu": [ "riscv64" ], @@ -878,9 +890,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", - "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.4.tgz", + "integrity": "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA==", "cpu": [ "s390x" ], @@ -895,9 +907,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", - "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.4.tgz", + "integrity": "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA==", "cpu": [ "x64" ], @@ -912,9 +924,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", - "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.4.tgz", + "integrity": "sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q==", "cpu": [ "arm64" ], @@ -929,9 +941,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", - "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.4.tgz", + "integrity": "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg==", "cpu": [ "x64" ], @@ -946,9 +958,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", - "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.4.tgz", + "integrity": "sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow==", "cpu": [ "arm64" ], @@ -963,9 +975,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", - "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.4.tgz", + "integrity": "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ==", "cpu": [ "x64" ], @@ -980,9 +992,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", - "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.4.tgz", + "integrity": "sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg==", "cpu": [ "arm64" ], @@ -997,9 +1009,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", - "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.4.tgz", + "integrity": "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g==", "cpu": [ "x64" ], @@ -1014,9 +1026,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", - "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.4.tgz", + "integrity": "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg==", "cpu": [ "arm64" ], @@ -1031,9 +1043,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", - "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.4.tgz", + "integrity": "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw==", "cpu": [ "ia32" ], @@ -1048,9 +1060,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", - "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.4.tgz", + "integrity": "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg==", "cpu": [ "x64" ], @@ -1094,20 +1106,27 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.21.1", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", - "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", "dev": true, "license": "Apache-2.0", "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", - "minimatch": "^3.1.2" + "minimatch": "^3.1.5" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@eslint/config-array/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, "node_modules/@eslint/config-array/node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", @@ -1159,20 +1178,20 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", - "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", + "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", "dev": true, "license": "MIT", "dependencies": { - "ajv": "^6.12.4", + "ajv": "^6.14.0", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.1", - "minimatch": "^3.1.2", + "minimatch": "^3.1.5", "strip-json-comments": "^3.1.1" }, "engines": { @@ -1182,6 +1201,13 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@eslint/eslintrc/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", @@ -1230,9 +1256,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.39.3", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.3.tgz", - "integrity": "sha512-1B1VkCq6FuUNlQvlBYb+1jDu/gV297TIs/OeiaSR9l1H27SVW55ONE1e1Vp16NqP683+xEGzxYtv4XCiDPaQiw==", + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", + "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", "dev": true, "license": "MIT", "engines": { @@ -1267,9 +1293,9 @@ } }, "node_modules/@exodus/bytes": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.14.1.tgz", - "integrity": "sha512-OhkBFWI6GcRMUroChZiopRiSp2iAMvEBK47NhJooDqz1RERO4QuZIZnjP63TXX8GAiLABkYmX+fuQsdJ1dd2QQ==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.0.tgz", + "integrity": "sha512-UY0nlA+feH81UGSHv92sLEPLCeZFjXOuHhrIo0HQydScuQc8s0A7kL/UdgwgDq8g8ilksmuoF35YVTNphV2aBQ==", "dev": true, "license": "MIT", "engines": { @@ -1284,56 +1310,55 @@ } } }, + "node_modules/@formatjs/bigdecimal": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@formatjs/bigdecimal/-/bigdecimal-0.2.0.tgz", + "integrity": "sha512-GeaxHZbUoYvHL9tC5eltHLs+1zU70aPw0s7LwqgktIzF5oMhNY4o4deEtusJMsq7WFJF3Ye2zQEzdG8beVk73w==", + "license": "MIT" + }, "node_modules/@formatjs/ecma402-abstract": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-3.1.1.tgz", - "integrity": "sha512-jhZbTwda+2tcNrs4kKvxrPLPjx8QsBCLCUgrrJ/S+G9YrGHWLhAyFMMBHJBnBoOwuLHd7L14FgYudviKaxkO2Q==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-3.2.0.tgz", + "integrity": "sha512-dHnqHgBo6GXYGRsepaE1wmsC2etaivOWd5VaJstZd+HI2zR3DCUjbDVZRtoPGkkXZmyHvBwrdEUuqfvzhF/DtQ==", "license": "MIT", "dependencies": { - "@formatjs/fast-memoize": "3.1.0", - "@formatjs/intl-localematcher": "0.8.1", - "decimal.js": "^10.6.0", - "tslib": "^2.8.1" + "@formatjs/bigdecimal": "0.2.0", + "@formatjs/fast-memoize": "3.1.1", + "@formatjs/intl-localematcher": "0.8.2" } }, "node_modules/@formatjs/fast-memoize": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-3.1.0.tgz", - "integrity": "sha512-b5mvSWCI+XVKiz5WhnBCY3RJ4ZwfjAidU0yVlKa3d3MSgKmH1hC3tBGEAtYyN5mqL7N0G5x0BOUYyO8CEupWgg==", - "license": "MIT", - "dependencies": { - "tslib": "^2.8.1" - } + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-3.1.1.tgz", + "integrity": "sha512-CbNbf+tlJn1baRnPkNePnBqTLxGliG6DDgNa/UtV66abwIjwsliPMOt0172tzxABYzSuxZBZfcp//qI8AvBWPg==", + "license": "MIT" }, "node_modules/@formatjs/icu-messageformat-parser": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-3.5.1.tgz", - "integrity": "sha512-sSDmSvmmoVQ92XqWb499KrIhv/vLisJU8ITFrx7T7NZHUmMY7EL9xgRowAosaljhqnj/5iufG24QrdzB6X3ItA==", + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-3.5.3.tgz", + "integrity": "sha512-HJWZ9S6JWey6iY5+YXE3Kd0ofWU1sC2KTTp56e1168g/xxWvVvr8k9G4fexIgwYV9wbtjY7kGYK5FjoWB3B2OQ==", "license": "MIT", "dependencies": { - "@formatjs/ecma402-abstract": "3.1.1", - "@formatjs/icu-skeleton-parser": "2.1.1", - "tslib": "^2.8.1" + "@formatjs/ecma402-abstract": "3.2.0", + "@formatjs/icu-skeleton-parser": "2.1.3" } }, "node_modules/@formatjs/icu-skeleton-parser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-2.1.1.tgz", - "integrity": "sha512-PSFABlcNefjI6yyk8f7nyX1DC7NHmq6WaCHZLySEXBrXuLOB2f935YsnzuPjlz+ibhb9yWTdPeVX1OVcj24w2Q==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-2.1.3.tgz", + "integrity": "sha512-9mFp8TJ166ZM2pcjKwsBWXrDnOJGT7vMEScVgLygUODPOsE8S6f/FHoacvrlHK1B4dYZk8vSCNruyPU64AfgJQ==", "license": "MIT", "dependencies": { - "@formatjs/ecma402-abstract": "3.1.1", - "tslib": "^2.8.1" + "@formatjs/ecma402-abstract": "3.2.0" } }, "node_modules/@formatjs/intl-localematcher": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.8.1.tgz", - "integrity": "sha512-xwEuwQFdtSq1UKtQnyTZWC+eHdv7Uygoa+H2k/9uzBVQjDyp9r20LNDNKedWXll7FssT3GRHvqsdJGYSUWqYFA==", + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.8.2.tgz", + "integrity": "sha512-q05KMYGJLyqFNFtIb8NhWLF5X3aK/k0wYt7dnRFuy6aLQL+vUwQ1cg5cO4qawEiINybeCPXAWlprY2mSBjSXAQ==", "license": "MIT", "dependencies": { - "@formatjs/fast-memoize": "3.1.0", - "tslib": "^2.8.1" + "@formatjs/fast-memoize": "3.1.1" } }, "node_modules/@humanfs/core": { @@ -1389,9 +1414,9 @@ } }, "node_modules/@img/colour": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz", - "integrity": "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", "license": "MIT", "optional": true, "engines": { @@ -1905,28 +1930,32 @@ } }, "node_modules/@napi-rs/wasm-runtime": { - "version": "0.2.12", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", - "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.1.tgz", + "integrity": "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==", "dev": true, "license": "MIT", "optional": true, "dependencies": { - "@emnapi/core": "^1.4.3", - "@emnapi/runtime": "^1.4.3", - "@tybys/wasm-util": "^0.10.0" + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1", + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" } }, "node_modules/@next/env": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/@next/env/-/env-16.2.0.tgz", - "integrity": "sha512-OZIbODWWAi0epQRCRjNe1VO45LOFBzgiyqmTLzIqWq6u1wrxKnAyz1HH6tgY/Mc81YzIjRPoYsPAEr4QV4l9TA==", + "version": "16.2.1", + "resolved": "https://registry.npmjs.org/@next/env/-/env-16.2.1.tgz", + "integrity": "sha512-n8P/HCkIWW+gVal2Z8XqXJ6aB3J0tuM29OcHpCsobWlChH/SITBs1DFBk/HajgrwDkqqBXPbuUuzgDvUekREPg==", "license": "MIT" }, "node_modules/@next/swc-darwin-arm64": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.2.0.tgz", - "integrity": "sha512-/JZsqKzKt01IFoiLLAzlNqys7qk2F3JkcUhj50zuRhKDQkZNOz9E5N6wAQWprXdsvjRP4lTFj+/+36NSv5AwhQ==", + "version": "16.2.1", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.2.1.tgz", + "integrity": "sha512-BwZ8w8YTaSEr2HIuXLMLxIdElNMPvY9fLqb20LX9A9OMGtJilhHLbCL3ggyd0TwjmMcTxi0XXt+ur1vWUoxj2Q==", "cpu": [ "arm64" ], @@ -1940,9 +1969,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.2.0.tgz", - "integrity": "sha512-/hV8erWq4SNlVgglUiW5UmQ5Hwy5EW/AbbXlJCn6zkfKxTy/E/U3V8U1Ocm2YCTUoFgQdoMxRyRMOW5jYy4ygg==", + "version": "16.2.1", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.2.1.tgz", + "integrity": "sha512-/vrcE6iQSJq3uL3VGVHiXeaKbn8Es10DGTGRJnRZlkNQQk3kaNtAJg8Y6xuAlrx/6INKVjkfi5rY0iEXorZ6uA==", "cpu": [ "x64" ], @@ -1956,9 +1985,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.2.0.tgz", - "integrity": "sha512-GkjL/Q7MWOwqWR9zoxu1TIHzkOI2l2BHCf7FzeQG87zPgs+6WDh+oC9Sw9ARuuL/FUk6JNCgKRkA6rEQYadUaw==", + "version": "16.2.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.2.1.tgz", + "integrity": "sha512-uLn+0BK+C31LTVbQ/QU+UaVrV0rRSJQ8RfniQAHPghDdgE+SlroYqcmFnO5iNjNfVWCyKZHYrs3Nl0mUzWxbBw==", "cpu": [ "arm64" ], @@ -1972,9 +2001,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.2.0.tgz", - "integrity": "sha512-1ffhC6KY5qWLg5miMlKJp3dZbXelEfjuXt1qcp5WzSCQy36CV3y+JT7OC1WSFKizGQCDOcQbfkH/IjZP3cdRNA==", + "version": "16.2.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.2.1.tgz", + "integrity": "sha512-ssKq6iMRnHdnycGp9hCuGnXJZ0YPr4/wNwrfE5DbmvEcgl9+yv97/Kq3TPVDfYome1SW5geciLB9aiEqKXQjlQ==", "cpu": [ "arm64" ], @@ -1988,9 +2017,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.2.0.tgz", - "integrity": "sha512-FmbDcZQ8yJRq93EJSL6xaE0KK/Rslraf8fj1uViGxg7K4CKBCRYSubILJPEhjSgZurpcPQq12QNOJQ0DRJl6Hg==", + "version": "16.2.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.2.1.tgz", + "integrity": "sha512-HQm7SrHRELJ30T1TSmT706IWovFFSRGxfgUkyWJZF/RKBMdbdRWJuFrcpDdE5vy9UXjFOx6L3mRdqH04Mmx0hg==", "cpu": [ "x64" ], @@ -2004,9 +2033,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.2.0.tgz", - "integrity": "sha512-HzjIHVkmGAwRbh/vzvoBWWEbb8BBZPxBvVbDQDvzHSf3D8RP/4vjw7MNLDXFF9Q1WEzeQyEj2zdxBtVAHu5Oyw==", + "version": "16.2.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.2.1.tgz", + "integrity": "sha512-aV2iUaC/5HGEpbBkE+4B8aHIudoOy5DYekAKOMSHoIYQ66y/wIVeaRx8MS2ZMdxe/HIXlMho4ubdZs/J8441Tg==", "cpu": [ "x64" ], @@ -2020,9 +2049,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.2.0.tgz", - "integrity": "sha512-UMiFNQf5H7+1ZsZPxEsA064WEuFbRNq/kEXyepbCnSErp4f5iut75dBA8UeerFIG3vDaQNOfCpevnERPp2V+nA==", + "version": "16.2.1", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.2.1.tgz", + "integrity": "sha512-IXdNgiDHaSk0ZUJ+xp0OQTdTgnpx1RCfRTalhn3cjOP+IddTMINwA7DXZrwTmGDO8SUr5q2hdP/du4DcrB1GxA==", "cpu": [ "arm64" ], @@ -2036,9 +2065,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.2.0.tgz", - "integrity": "sha512-DRrNJKW+/eimrZgdhVN1uvkN1OI4j6Lpefwr44jKQ0YQzztlmOBUUzHuV5GxOMPK3nmodAYElUVCY8ZXo/IWeA==", + "version": "16.2.1", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.2.1.tgz", + "integrity": "sha512-qvU+3a39Hay+ieIztkGSbF7+mccbbg1Tk25hc4JDylf8IHjYmY/Zm64Qq1602yPyQqvie+vf5T/uPwNxDNIoeg==", "cpu": [ "x64" ], @@ -2346,18 +2375,6 @@ "url": "https://opencollective.com/parcel" } }, - "node_modules/@parcel/watcher/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/@playwright/test": { "version": "1.58.2", "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.2.tgz", @@ -2390,9 +2407,9 @@ "license": "MIT" }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", - "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.0.tgz", + "integrity": "sha512-WOhNW9K8bR3kf4zLxbfg6Pxu2ybOUbB2AjMDHSQx86LIF4rH4Ft7vmMwNt0loO0eonglSNy4cpD3MKXXKQu0/A==", "cpu": [ "arm" ], @@ -2404,9 +2421,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", - "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.0.tgz", + "integrity": "sha512-u6JHLll5QKRvjciE78bQXDmqRqNs5M/3GVqZeMwvmjaNODJih/WIrJlFVEihvV0MiYFmd+ZyPr9wxOVbPAG2Iw==", "cpu": [ "arm64" ], @@ -2418,9 +2435,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", - "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.0.tgz", + "integrity": "sha512-qEF7CsKKzSRc20Ciu2Zw1wRrBz4g56F7r/vRwY430UPp/nt1x21Q/fpJ9N5l47WWvJlkNCPJz3QRVw008fi7yA==", "cpu": [ "arm64" ], @@ -2432,9 +2449,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", - "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.0.tgz", + "integrity": "sha512-WADYozJ4QCnXCH4wPB+3FuGmDPoFseVCUrANmA5LWwGmC6FL14BWC7pcq+FstOZv3baGX65tZ378uT6WG8ynTw==", "cpu": [ "x64" ], @@ -2446,9 +2463,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", - "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.0.tgz", + "integrity": "sha512-6b8wGHJlDrGeSE3aH5mGNHBjA0TTkxdoNHik5EkvPHCt351XnigA4pS7Wsj/Eo9Y8RBU6f35cjN9SYmCFBtzxw==", "cpu": [ "arm64" ], @@ -2460,9 +2477,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", - "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.0.tgz", + "integrity": "sha512-h25Ga0t4jaylMB8M/JKAyrvvfxGRjnPQIR8lnCayyzEjEOx2EJIlIiMbhpWxDRKGKF8jbNH01NnN663dH638mA==", "cpu": [ "x64" ], @@ -2474,9 +2491,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", - "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.0.tgz", + "integrity": "sha512-RzeBwv0B3qtVBWtcuABtSuCzToo2IEAIQrcyB/b2zMvBWVbjo8bZDjACUpnaafaxhTw2W+imQbP2BD1usasK4g==", "cpu": [ "arm" ], @@ -2488,9 +2505,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", - "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.0.tgz", + "integrity": "sha512-Sf7zusNI2CIU1HLzuu9Tc5YGAHEZs5Lu7N1ssJG4Tkw6e0MEsN7NdjUDDfGNHy2IU+ENyWT+L2obgWiguWibWQ==", "cpu": [ "arm" ], @@ -2502,9 +2519,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", - "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.0.tgz", + "integrity": "sha512-DX2x7CMcrJzsE91q7/O02IJQ5/aLkVtYFryqCjduJhUfGKG6yJV8hxaw8pZa93lLEpPTP/ohdN4wFz7yp/ry9A==", "cpu": [ "arm64" ], @@ -2516,9 +2533,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", - "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.0.tgz", + "integrity": "sha512-09EL+yFVbJZlhcQfShpswwRZ0Rg+z/CsSELFCnPt3iK+iqwGsI4zht3secj5vLEs957QvFFXnzAT0FFPIxSrkQ==", "cpu": [ "arm64" ], @@ -2530,9 +2547,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", - "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.0.tgz", + "integrity": "sha512-i9IcCMPr3EXm8EQg5jnja0Zyc1iFxJjZWlb4wr7U2Wx/GrddOuEafxRdMPRYVaXjgbhvqalp6np07hN1w9kAKw==", "cpu": [ "loong64" ], @@ -2544,9 +2561,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", - "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.0.tgz", + "integrity": "sha512-DGzdJK9kyJ+B78MCkWeGnpXJ91tK/iKA6HwHxF4TAlPIY7GXEvMe8hBFRgdrR9Ly4qebR/7gfUs9y2IoaVEyog==", "cpu": [ "loong64" ], @@ -2558,9 +2575,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", - "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.0.tgz", + "integrity": "sha512-RwpnLsqC8qbS8z1H1AxBA1H6qknR4YpPR9w2XX0vo2Sz10miu57PkNcnHVaZkbqyw/kUWfKMI73jhmfi9BRMUQ==", "cpu": [ "ppc64" ], @@ -2572,9 +2589,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", - "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.0.tgz", + "integrity": "sha512-Z8pPf54Ly3aqtdWC3G4rFigZgNvd+qJlOE52fmko3KST9SoGfAdSRCwyoyG05q1HrrAblLbk1/PSIV+80/pxLg==", "cpu": [ "ppc64" ], @@ -2586,9 +2603,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", - "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.0.tgz", + "integrity": "sha512-3a3qQustp3COCGvnP4SvrMHnPQ9d1vzCakQVRTliaz8cIp/wULGjiGpbcqrkv0WrHTEp8bQD/B3HBjzujVWLOA==", "cpu": [ "riscv64" ], @@ -2600,9 +2617,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", - "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.0.tgz", + "integrity": "sha512-pjZDsVH/1VsghMJ2/kAaxt6dL0psT6ZexQVrijczOf+PeP2BUqTHYejk3l6TlPRydggINOeNRhvpLa0AYpCWSQ==", "cpu": [ "riscv64" ], @@ -2614,9 +2631,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", - "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.0.tgz", + "integrity": "sha512-3ObQs0BhvPgiUVZrN7gqCSvmFuMWvWvsjG5ayJ3Lraqv+2KhOsp+pUbigqbeWqueGIsnn+09HBw27rJ+gYK4VQ==", "cpu": [ "s390x" ], @@ -2628,9 +2645,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", - "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.0.tgz", + "integrity": "sha512-EtylprDtQPdS5rXvAayrNDYoJhIz1/vzN2fEubo3yLE7tfAw+948dO0g4M0vkTVFhKojnF+n6C8bDNe+gDRdTg==", "cpu": [ "x64" ], @@ -2642,9 +2659,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", - "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.0.tgz", + "integrity": "sha512-k09oiRCi/bHU9UVFqD17r3eJR9bn03TyKraCrlz5ULFJGdJGi7VOmm9jl44vOJvRJ6P7WuBi/s2A97LxxHGIdw==", "cpu": [ "x64" ], @@ -2656,9 +2673,9 @@ ] }, "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", - "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.0.tgz", + "integrity": "sha512-1o/0/pIhozoSaDJoDcec+IVLbnRtQmHwPV730+AOD29lHEEo4F5BEUB24H0OBdhbBBDwIOSuf7vgg0Ywxdfiiw==", "cpu": [ "x64" ], @@ -2670,9 +2687,9 @@ ] }, "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", - "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.0.tgz", + "integrity": "sha512-pESDkos/PDzYwtyzB5p/UoNU/8fJo68vcXM9ZW2V0kjYayj1KaaUfi1NmTUTUpMn4UhU4gTuK8gIaFO4UGuMbA==", "cpu": [ "arm64" ], @@ -2684,9 +2701,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", - "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.0.tgz", + "integrity": "sha512-hj1wFStD7B1YBeYmvY+lWXZ7ey73YGPcViMShYikqKT1GtstIKQAtfUI6yrzPjAy/O7pO0VLXGmUVWXQMaYgTQ==", "cpu": [ "arm64" ], @@ -2698,9 +2715,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", - "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.0.tgz", + "integrity": "sha512-SyaIPFoxmUPlNDq5EHkTbiKzmSEmq/gOYFI/3HHJ8iS/v1mbugVa7dXUzcJGQfoytp9DJFLhHH4U3/eTy2Bq4w==", "cpu": [ "ia32" ], @@ -2712,9 +2729,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", - "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.0.tgz", + "integrity": "sha512-RdcryEfzZr+lAr5kRm2ucN9aVlCCa2QNq4hXelZxb8GG0NJSazq44Z3PCCc8wISRuCVnGs0lQJVX5Vp6fKA+IA==", "cpu": [ "x64" ], @@ -2726,9 +2743,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", - "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.0.tgz", + "integrity": "sha512-PrsWNQ8BuE00O3Xsx3ALh2Df8fAj9+cvvX9AIA6o4KpATR98c9mud4XtDWVvsEuyia5U4tVSTKygawyJkjm60w==", "cpu": [ "x64" ], @@ -2753,9 +2770,9 @@ "license": "MIT" }, "node_modules/@swc/core-darwin-arm64": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.11.tgz", - "integrity": "sha512-QoIupRWVH8AF1TgxYyeA5nS18dtqMuxNwchjBIwJo3RdwLEFiJq6onOx9JAxHtuPwUkIVuU2Xbp+jCJ7Vzmgtg==", + "version": "1.15.21", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.21.tgz", + "integrity": "sha512-SA8SFg9dp0qKRH8goWsax6bptFE2EdmPf2YRAQW9WoHGf3XKM1bX0nd5UdwxmC5hXsBUZAYf7xSciCler6/oyA==", "cpu": [ "arm64" ], @@ -2769,9 +2786,9 @@ } }, "node_modules/@swc/core-darwin-x64": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.11.tgz", - "integrity": "sha512-S52Gu1QtPSfBYDiejlcfp9GlN+NjTZBRRNsz8PNwBgSE626/FUf2PcllVUix7jqkoMC+t0rS8t+2/aSWlMuQtA==", + "version": "1.15.21", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.21.tgz", + "integrity": "sha512-//fOVntgowz9+V90lVsNCtyyrtbHp3jWH6Rch7MXHXbcvbLmbCTmssl5DeedUWLLGiAAW1wksBdqdGYOTjaNLw==", "cpu": [ "x64" ], @@ -2785,9 +2802,9 @@ } }, "node_modules/@swc/core-linux-arm-gnueabihf": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.11.tgz", - "integrity": "sha512-lXJs8oXo6Z4yCpimpQ8vPeCjkgoHu5NoMvmJZ8qxDyU99KVdg6KwU9H79vzrmB+HfH+dCZ7JGMqMF//f8Cfvdg==", + "version": "1.15.21", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.21.tgz", + "integrity": "sha512-meNI4Sh6h9h8DvIfEc0l5URabYMSuNvyisLmG6vnoYAS43s8ON3NJR8sDHvdP7NJTrLe0q/x2XCn6yL/BeHcZg==", "cpu": [ "arm" ], @@ -2801,9 +2818,9 @@ } }, "node_modules/@swc/core-linux-arm64-gnu": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.11.tgz", - "integrity": "sha512-chRsz1K52/vj8Mfq/QOugVphlKPWlMh10V99qfH41hbGvwAU6xSPd681upO4bKiOr9+mRIZZW+EfJqY42ZzRyA==", + "version": "1.15.21", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.21.tgz", + "integrity": "sha512-QrXlNQnHeXqU2EzLlnsPoWEh8/GtNJLvfMiPsDhk+ht6Xv8+vhvZ5YZ/BokNWSIZiWPKLAqR0M7T92YF5tmD3g==", "cpu": [ "arm64" ], @@ -2817,9 +2834,9 @@ } }, "node_modules/@swc/core-linux-arm64-musl": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.11.tgz", - "integrity": "sha512-PYftgsTaGnfDK4m6/dty9ryK1FbLk+LosDJ/RJR2nkXGc8rd+WenXIlvHjWULiBVnS1RsjHHOXmTS4nDhe0v0w==", + "version": "1.15.21", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.21.tgz", + "integrity": "sha512-8/yGCMO333ultDaMQivE5CjO6oXDPeeg1IV4sphojPkb0Pv0i6zvcRIkgp60xDB+UxLr6VgHgt+BBgqS959E9g==", "cpu": [ "arm64" ], @@ -2832,10 +2849,42 @@ "node": ">=10" } }, + "node_modules/@swc/core-linux-ppc64-gnu": { + "version": "1.15.21", + "resolved": "https://registry.npmjs.org/@swc/core-linux-ppc64-gnu/-/core-linux-ppc64-gnu-1.15.21.tgz", + "integrity": "sha512-ucW0HzPx0s1dgRvcvuLSPSA/2Kk/VYTv9st8qe1Kc22Gu0Q0rH9+6TcBTmMuNIp0Xs4BPr1uBttmbO1wEGI49Q==", + "cpu": [ + "ppc64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-s390x-gnu": { + "version": "1.15.21", + "resolved": "https://registry.npmjs.org/@swc/core-linux-s390x-gnu/-/core-linux-s390x-gnu-1.15.21.tgz", + "integrity": "sha512-ulTnOGc5I7YRObE/9NreAhQg94QkiR5qNhhcUZ1iFAYjzg/JGAi1ch+s/Ixe61pMIr8bfVrF0NOaB0f8wjaAfA==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, "node_modules/@swc/core-linux-x64-gnu": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.11.tgz", - "integrity": "sha512-DKtnJKIHiZdARyTKiX7zdRjiDS1KihkQWatQiCHMv+zc2sfwb4Glrodx2VLOX4rsa92NLR0Sw8WLcPEMFY1szQ==", + "version": "1.15.21", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.21.tgz", + "integrity": "sha512-D0RokxtM+cPvSqJIKR6uja4hbD+scI9ezo95mBhfSyLUs9wnPPl26sLp1ZPR/EXRdYm3F3S6RUtVi+8QXhT24Q==", "cpu": [ "x64" ], @@ -2849,9 +2898,9 @@ } }, "node_modules/@swc/core-linux-x64-musl": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.11.tgz", - "integrity": "sha512-mUjjntHj4+8WBaiDe5UwRNHuEzLjIWBTSGTw0JT9+C9/Yyuh4KQqlcEQ3ro6GkHmBGXBFpGIj/o5VMyRWfVfWw==", + "version": "1.15.21", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.21.tgz", + "integrity": "sha512-nER8u7VeRfmU6fMDzl1NQAbbB/G7O2avmvCOwIul1uGkZ2/acbPH+DCL9h5+0yd/coNcxMBTL6NGepIew+7C2w==", "cpu": [ "x64" ], @@ -2865,9 +2914,9 @@ } }, "node_modules/@swc/core-win32-arm64-msvc": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.11.tgz", - "integrity": "sha512-ZkNNG5zL49YpaFzfl6fskNOSxtcZ5uOYmWBkY4wVAvgbSAQzLRVBp+xArGWh2oXlY/WgL99zQSGTv7RI5E6nzA==", + "version": "1.15.21", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.21.tgz", + "integrity": "sha512-+/AgNBnjYugUA8C0Do4YzymgvnGbztv7j8HKSQLvR/DQgZPoXQ2B3PqB2mTtGh/X5DhlJWiqnunN35JUgWcAeQ==", "cpu": [ "arm64" ], @@ -2881,9 +2930,9 @@ } }, "node_modules/@swc/core-win32-ia32-msvc": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.11.tgz", - "integrity": "sha512-6XnzORkZCQzvTQ6cPrU7iaT9+i145oLwnin8JrfsLG41wl26+5cNQ2XV3zcbrnFEV6esjOceom9YO1w9mGJByw==", + "version": "1.15.21", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.21.tgz", + "integrity": "sha512-IkSZj8PX/N4HcaFhMQtzmkV8YSnuNoJ0E6OvMwFiOfejPhiKXvl7CdDsn1f4/emYEIDO3fpgZW9DTaCRMDxaDA==", "cpu": [ "ia32" ], @@ -2897,9 +2946,9 @@ } }, "node_modules/@swc/core-win32-x64-msvc": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.11.tgz", - "integrity": "sha512-IQ2n6af7XKLL6P1gIeZACskSxK8jWtoKpJWLZmdXTDj1MGzktUy4i+FvpdtxFmJWNavRWH1VmTr6kAubRDHeKw==", + "version": "1.15.21", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.21.tgz", + "integrity": "sha512-zUyWso7OOENB6e1N1hNuNn8vbvLsTdKQ5WKLgt/JcBNfJhKy/6jmBmqI3GXk/MyvQKd5SLvP7A0F36p7TeDqvw==", "cpu": [ "x64" ], @@ -2937,49 +2986,49 @@ } }, "node_modules/@tailwindcss/node": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.1.tgz", - "integrity": "sha512-jlx6sLk4EOwO6hHe1oCGm1Q4AN/s0rSrTTPBGPM0/RQ6Uylwq17FuU8IeJJKEjtc6K6O07zsvP+gDO6MMWo7pg==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.2.tgz", + "integrity": "sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA==", "dev": true, "license": "MIT", "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.19.0", "jiti": "^2.6.1", - "lightningcss": "1.31.1", + "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", - "tailwindcss": "4.2.1" + "tailwindcss": "4.2.2" } }, "node_modules/@tailwindcss/oxide": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.1.tgz", - "integrity": "sha512-yv9jeEFWnjKCI6/T3Oq50yQEOqmpmpfzG1hcZsAOaXFQPfzWprWrlHSdGPEF3WQTi8zu8ohC9Mh9J470nT5pUw==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.2.tgz", + "integrity": "sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg==", "dev": true, "license": "MIT", "engines": { "node": ">= 20" }, "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.2.1", - "@tailwindcss/oxide-darwin-arm64": "4.2.1", - "@tailwindcss/oxide-darwin-x64": "4.2.1", - "@tailwindcss/oxide-freebsd-x64": "4.2.1", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.1", - "@tailwindcss/oxide-linux-arm64-gnu": "4.2.1", - "@tailwindcss/oxide-linux-arm64-musl": "4.2.1", - "@tailwindcss/oxide-linux-x64-gnu": "4.2.1", - "@tailwindcss/oxide-linux-x64-musl": "4.2.1", - "@tailwindcss/oxide-wasm32-wasi": "4.2.1", - "@tailwindcss/oxide-win32-arm64-msvc": "4.2.1", - "@tailwindcss/oxide-win32-x64-msvc": "4.2.1" + "@tailwindcss/oxide-android-arm64": "4.2.2", + "@tailwindcss/oxide-darwin-arm64": "4.2.2", + "@tailwindcss/oxide-darwin-x64": "4.2.2", + "@tailwindcss/oxide-freebsd-x64": "4.2.2", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.2", + "@tailwindcss/oxide-linux-arm64-gnu": "4.2.2", + "@tailwindcss/oxide-linux-arm64-musl": "4.2.2", + "@tailwindcss/oxide-linux-x64-gnu": "4.2.2", + "@tailwindcss/oxide-linux-x64-musl": "4.2.2", + "@tailwindcss/oxide-wasm32-wasi": "4.2.2", + "@tailwindcss/oxide-win32-arm64-msvc": "4.2.2", + "@tailwindcss/oxide-win32-x64-msvc": "4.2.2" } }, "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.1.tgz", - "integrity": "sha512-eZ7G1Zm5EC8OOKaesIKuw77jw++QJ2lL9N+dDpdQiAB/c/B2wDh0QPFHbkBVrXnwNugvrbJFk1gK2SsVjwWReg==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.2.tgz", + "integrity": "sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg==", "cpu": [ "arm64" ], @@ -2994,9 +3043,9 @@ } }, "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.1.tgz", - "integrity": "sha512-q/LHkOstoJ7pI1J0q6djesLzRvQSIfEto148ppAd+BVQK0JYjQIFSK3JgYZJa+Yzi0DDa52ZsQx2rqytBnf8Hw==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.2.tgz", + "integrity": "sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg==", "cpu": [ "arm64" ], @@ -3011,9 +3060,9 @@ } }, "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.1.tgz", - "integrity": "sha512-/f/ozlaXGY6QLbpvd/kFTro2l18f7dHKpB+ieXz+Cijl4Mt9AI2rTrpq7V+t04nK+j9XBQHnSMdeQRhbGyt6fw==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.2.tgz", + "integrity": "sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw==", "cpu": [ "x64" ], @@ -3028,9 +3077,9 @@ } }, "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.1.tgz", - "integrity": "sha512-5e/AkgYJT/cpbkys/OU2Ei2jdETCLlifwm7ogMC7/hksI2fC3iiq6OcXwjibcIjPung0kRtR3TxEITkqgn0TcA==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.2.tgz", + "integrity": "sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ==", "cpu": [ "x64" ], @@ -3045,9 +3094,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.1.tgz", - "integrity": "sha512-Uny1EcVTTmerCKt/1ZuKTkb0x8ZaiuYucg2/kImO5A5Y/kBz41/+j0gxUZl+hTF3xkWpDmHX+TaWhOtba2Fyuw==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.2.tgz", + "integrity": "sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ==", "cpu": [ "arm" ], @@ -3062,9 +3111,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.1.tgz", - "integrity": "sha512-CTrwomI+c7n6aSSQlsPL0roRiNMDQ/YzMD9EjcR+H4f0I1SQ8QqIuPnsVp7QgMkC1Qi8rtkekLkOFjo7OlEFRQ==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.2.tgz", + "integrity": "sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw==", "cpu": [ "arm64" ], @@ -3079,9 +3128,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.1.tgz", - "integrity": "sha512-WZA0CHRL/SP1TRbA5mp9htsppSEkWuQ4KsSUumYQnyl8ZdT39ntwqmz4IUHGN6p4XdSlYfJwM4rRzZLShHsGAQ==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.2.tgz", + "integrity": "sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag==", "cpu": [ "arm64" ], @@ -3096,9 +3145,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.1.tgz", - "integrity": "sha512-qMFzxI2YlBOLW5PhblzuSWlWfwLHaneBE0xHzLrBgNtqN6mWfs+qYbhryGSXQjFYB1Dzf5w+LN5qbUTPhW7Y5g==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.2.tgz", + "integrity": "sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg==", "cpu": [ "x64" ], @@ -3113,9 +3162,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.1.tgz", - "integrity": "sha512-5r1X2FKnCMUPlXTWRYpHdPYUY6a1Ar/t7P24OuiEdEOmms5lyqjDRvVY1yy9Rmioh+AunQ0rWiOTPE8F9A3v5g==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.2.tgz", + "integrity": "sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ==", "cpu": [ "x64" ], @@ -3130,9 +3179,9 @@ } }, "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.1.tgz", - "integrity": "sha512-MGFB5cVPvshR85MTJkEvqDUnuNoysrsRxd6vnk1Lf2tbiqNlXpHYZqkqOQalydienEWOHHFyyuTSYRsLfxFJ2Q==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.2.tgz", + "integrity": "sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q==", "bundleDependencies": [ "@napi-rs/wasm-runtime", "@emnapi/core", @@ -3160,9 +3209,9 @@ } }, "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.1.tgz", - "integrity": "sha512-YlUEHRHBGnCMh4Nj4GnqQyBtsshUPdiNroZj8VPkvTZSoHsilRCwXcVKnG9kyi0ZFAS/3u+qKHBdDc81SADTRA==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.2.tgz", + "integrity": "sha512-qPmaQM4iKu5mxpsrWZMOZRgZv1tOZpUm+zdhhQP0VhJfyGGO3aUKdbh3gDZc/dPLQwW4eSqWGrrcWNBZWUWaXQ==", "cpu": [ "arm64" ], @@ -3177,9 +3226,9 @@ } }, "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.1.tgz", - "integrity": "sha512-rbO34G5sMWWyrN/idLeVxAZgAKWrn5LiR3/I90Q9MkA67s6T1oB0xtTe+0heoBvHSpbU9Mk7i6uwJnpo4u21XQ==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.2.tgz", + "integrity": "sha512-1T/37VvI7WyH66b+vqHj/cLwnCxt7Qt3WFu5Q8hk65aOvlwAhs7rAp1VkulBJw/N4tMirXjVnylTR72uI0HGcA==", "cpu": [ "x64" ], @@ -3194,26 +3243,26 @@ } }, "node_modules/@tailwindcss/postcss": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.2.1.tgz", - "integrity": "sha512-OEwGIBnXnj7zJeonOh6ZG9woofIjGrd2BORfvE5p9USYKDCZoQmfqLcfNiRWoJlRWLdNPn2IgVZuWAOM4iTYMw==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.2.2.tgz", + "integrity": "sha512-n4goKQbW8RVXIbNKRB/45LzyUqN451deQK0nzIeauVEqjlI49slUlgKYJM2QyUzap/PcpnS7kzSUmPb1sCRvYQ==", "dev": true, "license": "MIT", "dependencies": { "@alloc/quick-lru": "^5.2.0", - "@tailwindcss/node": "4.2.1", - "@tailwindcss/oxide": "4.2.1", + "@tailwindcss/node": "4.2.2", + "@tailwindcss/oxide": "4.2.2", "postcss": "^8.5.6", - "tailwindcss": "4.2.1" + "tailwindcss": "4.2.2" } }, "node_modules/@tanstack/react-virtual": { - "version": "3.13.19", - "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.13.19.tgz", - "integrity": "sha512-KzwmU1IbE0IvCZSm6OXkS+kRdrgW2c2P3Ho3NC+zZXWK6oObv/L+lcV/2VuJ+snVESRlMJ+w/fg4WXI/JzoNGQ==", + "version": "3.13.23", + "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.13.23.tgz", + "integrity": "sha512-XnMRnHQ23piOVj2bzJqHrRrLg4r+F86fuBcwteKfbIjJrtGxb4z7tIvPVAe4B+4UVwo9G4Giuz5fmapcrnZ0OQ==", "license": "MIT", "dependencies": { - "@tanstack/virtual-core": "3.13.19" + "@tanstack/virtual-core": "3.13.23" }, "funding": { "type": "github", @@ -3225,9 +3274,9 @@ } }, "node_modules/@tanstack/virtual-core": { - "version": "3.13.19", - "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.13.19.tgz", - "integrity": "sha512-/BMP7kNhzKOd7wnDeB8NrIRNLwkf5AhCYCvtfZV2GXWbBieFm/el0n6LOAXlTi6ZwHICSNnQcIxRCWHrLzDY+g==", + "version": "3.13.23", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.13.23.tgz", + "integrity": "sha512-zSz2Z2HNyLjCplANTDyl3BcdQJc2k1+yyFoKhNRmCr7V7dY8o8q5m8uFTI1/Pg1kL+Hgrz6u3Xo6eFUB7l66cg==", "license": "MIT", "funding": { "type": "github", @@ -3406,9 +3455,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.3.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.3.tgz", - "integrity": "sha512-DpzbrH7wIcBaJibpKo9nnSQL0MTRdnWttGyE5haGwK86xgMOkFLp7vEyfQPGLOJh5wNYiJ3V9PmUMDhV9u8kkQ==", + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz", + "integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==", "dev": true, "license": "MIT", "peer": true, @@ -3446,17 +3495,17 @@ "optional": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.56.1.tgz", - "integrity": "sha512-Jz9ZztpB37dNC+HU2HI28Bs9QXpzCz+y/twHOwhyrIRdbuVDxSytJNDl6z/aAKlaRIwC7y8wJdkBv7FxYGgi0A==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.57.1.tgz", + "integrity": "sha512-Gn3aqnvNl4NGc6x3/Bqk1AOn0thyTU9bqDRhiRnUWezgvr2OnhYCWCgC8zXXRVqBsIL1pSDt7T9nJUe0oM0kDQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.56.1", - "@typescript-eslint/type-utils": "8.56.1", - "@typescript-eslint/utils": "8.56.1", - "@typescript-eslint/visitor-keys": "8.56.1", + "@typescript-eslint/scope-manager": "8.57.1", + "@typescript-eslint/type-utils": "8.57.1", + "@typescript-eslint/utils": "8.57.1", + "@typescript-eslint/visitor-keys": "8.57.1", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.4.0" @@ -3469,23 +3518,23 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.56.1", + "@typescript-eslint/parser": "^8.57.1", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/parser": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.56.1.tgz", - "integrity": "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.57.1.tgz", + "integrity": "sha512-k4eNDan0EIMTT/dUKc/g+rsJ6wcHYhNPdY19VoX/EOtaAG8DLtKCykhrUnuHPYvinn5jhAPgD2Qw9hXBwrahsw==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@typescript-eslint/scope-manager": "8.56.1", - "@typescript-eslint/types": "8.56.1", - "@typescript-eslint/typescript-estree": "8.56.1", - "@typescript-eslint/visitor-keys": "8.56.1", + "@typescript-eslint/scope-manager": "8.57.1", + "@typescript-eslint/types": "8.57.1", + "@typescript-eslint/typescript-estree": "8.57.1", + "@typescript-eslint/visitor-keys": "8.57.1", "debug": "^4.4.3" }, "engines": { @@ -3501,14 +3550,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.56.1.tgz", - "integrity": "sha512-TAdqQTzHNNvlVFfR+hu2PDJrURiwKsUvxFn1M0h95BB8ah5jejas08jUWG4dBA68jDMI988IvtfdAI53JzEHOQ==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.57.1.tgz", + "integrity": "sha512-vx1F37BRO1OftsYlmG9xay1TqnjNVlqALymwWVuYTdo18XuKxtBpCj1QlzNIEHlvlB27osvXFWptYiEWsVdYsg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.56.1", - "@typescript-eslint/types": "^8.56.1", + "@typescript-eslint/tsconfig-utils": "^8.57.1", + "@typescript-eslint/types": "^8.57.1", "debug": "^4.4.3" }, "engines": { @@ -3523,14 +3572,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.56.1.tgz", - "integrity": "sha512-YAi4VDKcIZp0O4tz/haYKhmIDZFEUPOreKbfdAN3SzUDMcPhJ8QI99xQXqX+HoUVq8cs85eRKnD+rne2UAnj2w==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.57.1.tgz", + "integrity": "sha512-hs/QcpCwlwT2L5S+3fT6gp0PabyGk4Q0Rv2doJXA0435/OpnSR3VRgvrp8Xdoc3UAYSg9cyUjTeFXZEPg/3OKg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.56.1", - "@typescript-eslint/visitor-keys": "8.56.1" + "@typescript-eslint/types": "8.57.1", + "@typescript-eslint/visitor-keys": "8.57.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3541,9 +3590,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.56.1.tgz", - "integrity": "sha512-qOtCYzKEeyr3aR9f28mPJqBty7+DBqsdd63eO0yyDwc6vgThj2UjWfJIcsFeSucYydqcuudMOprZ+x1SpF3ZuQ==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.57.1.tgz", + "integrity": "sha512-0lgOZB8cl19fHO4eI46YUx2EceQqhgkPSuCGLlGi79L2jwYY1cxeYc1Nae8Aw1xjgW3PKVDLlr3YJ6Bxx8HkWg==", "dev": true, "license": "MIT", "engines": { @@ -3558,15 +3607,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.56.1.tgz", - "integrity": "sha512-yB/7dxi7MgTtGhZdaHCemf7PuwrHMenHjmzgUW1aJpO+bBU43OycnM3Wn+DdvDO/8zzA9HlhaJ0AUGuvri4oGg==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.57.1.tgz", + "integrity": "sha512-+Bwwm0ScukFdyoJsh2u6pp4S9ktegF98pYUU0hkphOOqdMB+1sNQhIz8y5E9+4pOioZijrkfNO/HUJVAFFfPKA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.56.1", - "@typescript-eslint/typescript-estree": "8.56.1", - "@typescript-eslint/utils": "8.56.1", + "@typescript-eslint/types": "8.57.1", + "@typescript-eslint/typescript-estree": "8.57.1", + "@typescript-eslint/utils": "8.57.1", "debug": "^4.4.3", "ts-api-utils": "^2.4.0" }, @@ -3583,9 +3632,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.56.1.tgz", - "integrity": "sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.57.1.tgz", + "integrity": "sha512-S29BOBPJSFUiblEl6RzPPjJt6w25A6XsBqRVDt53tA/tlL8q7ceQNZHTjPeONt/3S7KRI4quk+yP9jK2WjBiPQ==", "dev": true, "license": "MIT", "engines": { @@ -3597,16 +3646,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.56.1.tgz", - "integrity": "sha512-qzUL1qgalIvKWAf9C1HpvBjif+Vm6rcT5wZd4VoMb9+Km3iS3Cv9DY6dMRMDtPnwRAFyAi7YXJpTIEXLvdfPxg==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.57.1.tgz", + "integrity": "sha512-ybe2hS9G6pXpqGtPli9Gx9quNV0TWLOmh58ADlmZe9DguLq0tiAKVjirSbtM1szG6+QH6rVXyU6GTLQbWnMY+g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.56.1", - "@typescript-eslint/tsconfig-utils": "8.56.1", - "@typescript-eslint/types": "8.56.1", - "@typescript-eslint/visitor-keys": "8.56.1", + "@typescript-eslint/project-service": "8.57.1", + "@typescript-eslint/tsconfig-utils": "8.57.1", + "@typescript-eslint/types": "8.57.1", + "@typescript-eslint/visitor-keys": "8.57.1", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", @@ -3625,16 +3674,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.56.1.tgz", - "integrity": "sha512-HPAVNIME3tABJ61siYlHzSWCGtOoeP2RTIaHXFMPqjrQKCGB9OgUVdiNgH7TJS2JNIQ5qQ4RsAUDuGaGme/KOA==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.57.1.tgz", + "integrity": "sha512-XUNSJ/lEVFttPMMoDVA2r2bwrl8/oPx8cURtczkSEswY5T3AeLmCy+EKWQNdL4u0MmAHOjcWrqJp2cdvgjn8dQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.56.1", - "@typescript-eslint/types": "8.56.1", - "@typescript-eslint/typescript-estree": "8.56.1" + "@typescript-eslint/scope-manager": "8.57.1", + "@typescript-eslint/types": "8.57.1", + "@typescript-eslint/typescript-estree": "8.57.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3649,13 +3698,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.56.1.tgz", - "integrity": "sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.57.1.tgz", + "integrity": "sha512-YWnmJkXbofiz9KbnbbwuA2rpGkFPLbAIetcCNO6mJ8gdhdZ/v7WDXsoGFAJuM6ikUFKTlSQnjWnVO4ux+UzS6A==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/types": "8.57.1", "eslint-visitor-keys": "^5.0.0" }, "engines": { @@ -3680,9 +3729,9 @@ } }, "node_modules/@vitejs/plugin-react": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.4.tgz", - "integrity": "sha512-VIcFLdRi/VYRU8OL/puL7QXMYafHmqOnwTZY50U1JPlCNj30PxCMx65c494b1K9be9hX83KVt0+gTEwTWLqToA==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.2.0.tgz", + "integrity": "sha512-YmKkfhOAi3wsB1PhJq5Scj3GXMn3WvtQ/JC0xoopuHoXSdmtdStOpFrYaT1kie2YgFBcIe64ROzMYRjCrYOdYw==", "dev": true, "license": "MIT", "dependencies": { @@ -3697,7 +3746,7 @@ "node": "^20.19.0 || >=22.12.0" }, "peerDependencies": { - "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" } }, "node_modules/@vitest/expect": { @@ -4103,16 +4152,19 @@ } }, "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } }, "node_modules/baseline-browser-mapping": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz", - "integrity": "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==", + "version": "2.10.10", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.10.tgz", + "integrity": "sha512-sUoJ3IMxx4AyRqO4MLeHlnGDkyXRoUG0/AI9fjK+vS72ekpV0yWVY7O0BVjmBcRtkNcsAO2QDZ4tdKKGoI6YaQ==", "license": "Apache-2.0", "bin": { "baseline-browser-mapping": "dist/cli.cjs" @@ -4144,16 +4196,6 @@ "node": "18 || 20 || >=22" } }, - "node_modules/brace-expansion/node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, "node_modules/browserslist": { "version": "4.28.1", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", @@ -4250,9 +4292,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001772", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001772.tgz", - "integrity": "sha512-mIwLZICj+ntVTw4BT2zfp+yu/AqV6GMKfJVJMx3MwPxs+uk/uj2GLl2dH8LQbjiLDX66amCga5nKFyDgRR43kg==", + "version": "1.0.30001781", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001781.tgz", + "integrity": "sha512-RdwNCyMsNBftLjW6w01z8bKEvT6e/5tpPVEgtn22TiLGlstHOVecsX2KHFkD5e/vRnIE4EGzpuIODb3mtswtkw==", "funding": [ { "type": "opencollective", @@ -4361,14 +4403,14 @@ } }, "node_modules/css-tree": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz", - "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", "dev": true, "license": "MIT", "dependencies": { - "mdn-data": "2.12.2", - "source-map-js": "^1.0.1" + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" @@ -4382,25 +4424,25 @@ "license": "MIT" }, "node_modules/cssstyle": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-6.0.2.tgz", - "integrity": "sha512-B5xvQYh7n+s/elmwhMOthufrO+QaORHuUoSJGhmogxPz9LNT6HMbou3fUeieOOFogKP84SYryyLC405QvyFvaA==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-6.2.0.tgz", + "integrity": "sha512-Fm5NvhYathRnXNVndkUsCCuR63DCLVVwGOOwQw782coXFi5HhkXdu289l59HlXZBawsyNccXfWRYvLzcDCdDig==", "dev": true, "license": "MIT", "dependencies": { - "@asamuzakjp/css-color": "^4.1.2", - "@csstools/css-syntax-patches-for-csstree": "^1.0.26", + "@asamuzakjp/css-color": "^5.0.1", + "@csstools/css-syntax-patches-for-csstree": "^1.0.28", "css-tree": "^3.1.0", - "lru-cache": "^11.2.5" + "lru-cache": "^11.2.6" }, "engines": { "node": ">=20" } }, "node_modules/cssstyle/node_modules/lru-cache": { - "version": "11.2.6", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", - "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", + "version": "11.2.7", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", + "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", "dev": true, "license": "BlueOak-1.0.0", "engines": { @@ -4514,6 +4556,7 @@ "version": "10.6.0", "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, "license": "MIT" }, "node_modules/deep-is": { @@ -4623,16 +4666,16 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.302", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.302.tgz", - "integrity": "sha512-sM6HAN2LyK82IyPBpznDRqlTQAtuSaO+ShzFiWTvoMJLHyZ+Y39r8VMfHzwbU8MVBzQ4Wdn85+wlZl2TLGIlwg==", + "version": "1.5.321", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.321.tgz", + "integrity": "sha512-L2C7Q279W2D/J4PLZLk7sebOILDSWos7bMsMNN06rK482umHUrh/3lM8G7IlHFOYip2oAg5nha1rCMxr/rs6ZQ==", "dev": true, "license": "ISC" }, "node_modules/enhanced-resolve": { - "version": "5.20.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.0.tgz", - "integrity": "sha512-/ce7+jQ1PQ6rVXwe+jKEg5hW5ciicHwIQUagZkp6IufBoY3YDgdTTY1azVs0qoRgVmvsNB+rbjLJxDAeHHtwsQ==", + "version": "5.20.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.1.tgz", + "integrity": "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==", "dev": true, "license": "MIT", "dependencies": { @@ -4746,9 +4789,9 @@ } }, "node_modules/es-iterator-helpers": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.2.tgz", - "integrity": "sha512-BrUQ0cPTB/IwXj23HtwHjS9n7O4h9FX94b4xc5zlTHxeLgTAdzYUDyy6KdExAl9lbN5rtfe44xpjpmj9grxs5w==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.3.1.tgz", + "integrity": "sha512-zWwRvqWiuBPr0muUG/78cW3aHROFCNIQ3zpmYDpwdbnt2m+xlNyRWpHBpa2lJjSBit7BQ+RXA1iwbSmu5yJ/EQ==", "dev": true, "license": "MIT", "dependencies": { @@ -4767,6 +4810,7 @@ "has-symbols": "^1.1.0", "internal-slot": "^1.1.0", "iterator.prototype": "^1.1.5", + "math-intrinsics": "^1.1.0", "safe-array-concat": "^1.1.3" }, "engines": { @@ -4841,9 +4885,9 @@ } }, "node_modules/esbuild": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", - "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.4.tgz", + "integrity": "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -4854,32 +4898,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.3", - "@esbuild/android-arm": "0.27.3", - "@esbuild/android-arm64": "0.27.3", - "@esbuild/android-x64": "0.27.3", - "@esbuild/darwin-arm64": "0.27.3", - "@esbuild/darwin-x64": "0.27.3", - "@esbuild/freebsd-arm64": "0.27.3", - "@esbuild/freebsd-x64": "0.27.3", - "@esbuild/linux-arm": "0.27.3", - "@esbuild/linux-arm64": "0.27.3", - "@esbuild/linux-ia32": "0.27.3", - "@esbuild/linux-loong64": "0.27.3", - "@esbuild/linux-mips64el": "0.27.3", - "@esbuild/linux-ppc64": "0.27.3", - "@esbuild/linux-riscv64": "0.27.3", - "@esbuild/linux-s390x": "0.27.3", - "@esbuild/linux-x64": "0.27.3", - "@esbuild/netbsd-arm64": "0.27.3", - "@esbuild/netbsd-x64": "0.27.3", - "@esbuild/openbsd-arm64": "0.27.3", - "@esbuild/openbsd-x64": "0.27.3", - "@esbuild/openharmony-arm64": "0.27.3", - "@esbuild/sunos-x64": "0.27.3", - "@esbuild/win32-arm64": "0.27.3", - "@esbuild/win32-ia32": "0.27.3", - "@esbuild/win32-x64": "0.27.3" + "@esbuild/aix-ppc64": "0.27.4", + "@esbuild/android-arm": "0.27.4", + "@esbuild/android-arm64": "0.27.4", + "@esbuild/android-x64": "0.27.4", + "@esbuild/darwin-arm64": "0.27.4", + "@esbuild/darwin-x64": "0.27.4", + "@esbuild/freebsd-arm64": "0.27.4", + "@esbuild/freebsd-x64": "0.27.4", + "@esbuild/linux-arm": "0.27.4", + "@esbuild/linux-arm64": "0.27.4", + "@esbuild/linux-ia32": "0.27.4", + "@esbuild/linux-loong64": "0.27.4", + "@esbuild/linux-mips64el": "0.27.4", + "@esbuild/linux-ppc64": "0.27.4", + "@esbuild/linux-riscv64": "0.27.4", + "@esbuild/linux-s390x": "0.27.4", + "@esbuild/linux-x64": "0.27.4", + "@esbuild/netbsd-arm64": "0.27.4", + "@esbuild/netbsd-x64": "0.27.4", + "@esbuild/openbsd-arm64": "0.27.4", + "@esbuild/openbsd-x64": "0.27.4", + "@esbuild/openharmony-arm64": "0.27.4", + "@esbuild/sunos-x64": "0.27.4", + "@esbuild/win32-arm64": "0.27.4", + "@esbuild/win32-ia32": "0.27.4", + "@esbuild/win32-x64": "0.27.4" } }, "node_modules/escalade": { @@ -4906,26 +4950,26 @@ } }, "node_modules/eslint": { - "version": "9.39.3", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.3.tgz", - "integrity": "sha512-VmQ+sifHUbI/IcSopBCF/HO3YiHQx/AVd3UVyYL6weuwW+HvON9VYn5l6Zl1WZzPWXPNZrSQpxwkkZ/VuvJZzg==", + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", + "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", "dev": true, "license": "MIT", "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.21.1", + "@eslint/config-array": "^0.21.2", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", - "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.39.3", + "@eslint/eslintrc": "^3.3.5", + "@eslint/js": "9.39.4", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", - "ajv": "^6.12.4", + "ajv": "^6.14.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", @@ -4944,7 +4988,7 @@ "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", + "minimatch": "^3.1.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, @@ -5019,6 +5063,13 @@ "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" } }, + "node_modules/eslint-plugin-react/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, "node_modules/eslint-plugin-react/node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", @@ -5043,30 +5094,6 @@ "node": "*" } }, - "node_modules/eslint-plugin-react/node_modules/resolve": { - "version": "2.0.0-next.6", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.6.tgz", - "integrity": "sha512-3JmVl5hMGtJ3kMmB3zi3DL25KfkCEyy3Tw7Gmw7z5w8M9WlwoPFnIvwChzu1+cF3iaK3sp18hhPz8ANeimdJfA==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "is-core-module": "^2.16.1", - "node-exports-info": "^1.6.0", - "object-keys": "^1.1.1", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/eslint-plugin-react/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -5107,6 +5134,13 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, "node_modules/eslint/node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", @@ -5272,6 +5306,24 @@ "dev": true, "license": "MIT" }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, "node_modules/fflate": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", @@ -5788,15 +5840,14 @@ } }, "node_modules/intl-messageformat": { - "version": "11.1.2", - "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-11.1.2.tgz", - "integrity": "sha512-ucSrQmZGAxfiBHfBRXW/k7UC8MaGFlEj4Ry1tKiDcmgwQm1y3EDl40u+4VNHYomxJQMJi9NEI3riDRlth96jKg==", + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-11.2.0.tgz", + "integrity": "sha512-IhghAA8n4KSlXuWKzYsWyWb82JoYTzShfyvdSF85oJPnNOjvv4kAo7S7Jtkm3/vJ53C7dQNRO+Gpnj3iWgTjBQ==", "license": "BSD-3-Clause", "dependencies": { - "@formatjs/ecma402-abstract": "3.1.1", - "@formatjs/fast-memoize": "3.1.0", - "@formatjs/icu-messageformat-parser": "3.5.1", - "tslib": "^2.8.1" + "@formatjs/ecma402-abstract": "3.2.0", + "@formatjs/fast-memoize": "3.1.1", + "@formatjs/icu-messageformat-parser": "3.5.3" } }, "node_modules/is-array-buffer": { @@ -6378,9 +6429,9 @@ } }, "node_modules/lightningcss": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.31.1.tgz", - "integrity": "sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", "dev": true, "license": "MPL-2.0", "dependencies": { @@ -6394,23 +6445,23 @@ "url": "https://opencollective.com/parcel" }, "optionalDependencies": { - "lightningcss-android-arm64": "1.31.1", - "lightningcss-darwin-arm64": "1.31.1", - "lightningcss-darwin-x64": "1.31.1", - "lightningcss-freebsd-x64": "1.31.1", - "lightningcss-linux-arm-gnueabihf": "1.31.1", - "lightningcss-linux-arm64-gnu": "1.31.1", - "lightningcss-linux-arm64-musl": "1.31.1", - "lightningcss-linux-x64-gnu": "1.31.1", - "lightningcss-linux-x64-musl": "1.31.1", - "lightningcss-win32-arm64-msvc": "1.31.1", - "lightningcss-win32-x64-msvc": "1.31.1" + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" } }, "node_modules/lightningcss-android-arm64": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.31.1.tgz", - "integrity": "sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", "cpu": [ "arm64" ], @@ -6429,9 +6480,9 @@ } }, "node_modules/lightningcss-darwin-arm64": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.31.1.tgz", - "integrity": "sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", "cpu": [ "arm64" ], @@ -6450,9 +6501,9 @@ } }, "node_modules/lightningcss-darwin-x64": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.31.1.tgz", - "integrity": "sha512-1ObhyoCY+tGxtsz1lSx5NXCj3nirk0Y0kB/g8B8DT+sSx4G9djitg9ejFnjb3gJNWo7qXH4DIy2SUHvpoFwfTA==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", "cpu": [ "x64" ], @@ -6471,9 +6522,9 @@ } }, "node_modules/lightningcss-freebsd-x64": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.31.1.tgz", - "integrity": "sha512-1RINmQKAItO6ISxYgPwszQE1BrsVU5aB45ho6O42mu96UiZBxEXsuQ7cJW4zs4CEodPUioj/QrXW1r9pLUM74A==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", "cpu": [ "x64" ], @@ -6492,9 +6543,9 @@ } }, "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.31.1.tgz", - "integrity": "sha512-OOCm2//MZJ87CdDK62rZIu+aw9gBv4azMJuA8/KB74wmfS3lnC4yoPHm0uXZ/dvNNHmnZnB8XLAZzObeG0nS1g==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", "cpu": [ "arm" ], @@ -6513,9 +6564,9 @@ } }, "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.31.1.tgz", - "integrity": "sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", "cpu": [ "arm64" ], @@ -6534,9 +6585,9 @@ } }, "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.31.1.tgz", - "integrity": "sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", "cpu": [ "arm64" ], @@ -6555,9 +6606,9 @@ } }, "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.31.1.tgz", - "integrity": "sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", "cpu": [ "x64" ], @@ -6576,9 +6627,9 @@ } }, "node_modules/lightningcss-linux-x64-musl": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.31.1.tgz", - "integrity": "sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", "cpu": [ "x64" ], @@ -6597,9 +6648,9 @@ } }, "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.31.1.tgz", - "integrity": "sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", "cpu": [ "arm64" ], @@ -6618,9 +6669,9 @@ } }, "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.31.1.tgz", - "integrity": "sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", "cpu": [ "x64" ], @@ -6724,9 +6775,9 @@ } }, "node_modules/mdn-data": { - "version": "2.12.2", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz", - "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==", + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", "dev": true, "license": "CC0-1.0" }, @@ -6808,12 +6859,12 @@ } }, "node_modules/next": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/next/-/next-16.2.0.tgz", - "integrity": "sha512-NLBVrJy1pbV1Yn00L5sU4vFyAHt5XuSjzrNyFnxo6Com0M0KrL6hHM5B99dbqXb2bE9pm4Ow3Zl1xp6HVY9edQ==", + "version": "16.2.1", + "resolved": "https://registry.npmjs.org/next/-/next-16.2.1.tgz", + "integrity": "sha512-VaChzNL7o9rbfdt60HUj8tev4m6d7iC1igAy157526+cJlXOQu5LzsBXNT+xaJnTP/k+utSX5vMv7m0G+zKH+Q==", "license": "MIT", "dependencies": { - "@next/env": "16.2.0", + "@next/env": "16.2.1", "@swc/helpers": "0.5.15", "baseline-browser-mapping": "^2.9.19", "caniuse-lite": "^1.0.30001579", @@ -6827,14 +6878,14 @@ "node": ">=20.9.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "16.2.0", - "@next/swc-darwin-x64": "16.2.0", - "@next/swc-linux-arm64-gnu": "16.2.0", - "@next/swc-linux-arm64-musl": "16.2.0", - "@next/swc-linux-x64-gnu": "16.2.0", - "@next/swc-linux-x64-musl": "16.2.0", - "@next/swc-win32-arm64-msvc": "16.2.0", - "@next/swc-win32-x64-msvc": "16.2.0", + "@next/swc-darwin-arm64": "16.2.1", + "@next/swc-darwin-x64": "16.2.1", + "@next/swc-linux-arm64-gnu": "16.2.1", + "@next/swc-linux-arm64-musl": "16.2.1", + "@next/swc-linux-x64-gnu": "16.2.1", + "@next/swc-linux-x64-musl": "16.2.1", + "@next/swc-win32-arm64-msvc": "16.2.1", + "@next/swc-win32-x64-msvc": "16.2.1", "sharp": "^0.34.5" }, "peerDependencies": { @@ -6899,9 +6950,9 @@ "license": "MIT" }, "node_modules/next-intl/node_modules/@swc/core": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.11.tgz", - "integrity": "sha512-iLmLTodbYxU39HhMPaMUooPwO/zqJWvsqkrXv1ZI38rMb048p6N7qtAtTp37sw9NzSrvH6oli8EdDygo09IZ/w==", + "version": "1.15.21", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.21.tgz", + "integrity": "sha512-fkk7NJcBscrR3/F8jiqlMptRHP650NxqDnspBMrRe5d8xOoCy9MLL5kOBLFXjFLfMo3KQQHhk+/jUULOMlR1uQ==", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { @@ -6916,16 +6967,18 @@ "url": "https://opencollective.com/swc" }, "optionalDependencies": { - "@swc/core-darwin-arm64": "1.15.11", - "@swc/core-darwin-x64": "1.15.11", - "@swc/core-linux-arm-gnueabihf": "1.15.11", - "@swc/core-linux-arm64-gnu": "1.15.11", - "@swc/core-linux-arm64-musl": "1.15.11", - "@swc/core-linux-x64-gnu": "1.15.11", - "@swc/core-linux-x64-musl": "1.15.11", - "@swc/core-win32-arm64-msvc": "1.15.11", - "@swc/core-win32-ia32-msvc": "1.15.11", - "@swc/core-win32-x64-msvc": "1.15.11" + "@swc/core-darwin-arm64": "1.15.21", + "@swc/core-darwin-x64": "1.15.21", + "@swc/core-linux-arm-gnueabihf": "1.15.21", + "@swc/core-linux-arm64-gnu": "1.15.21", + "@swc/core-linux-arm64-musl": "1.15.21", + "@swc/core-linux-ppc64-gnu": "1.15.21", + "@swc/core-linux-s390x-gnu": "1.15.21", + "@swc/core-linux-x64-gnu": "1.15.21", + "@swc/core-linux-x64-musl": "1.15.21", + "@swc/core-win32-arm64-msvc": "1.15.21", + "@swc/core-win32-ia32-msvc": "1.15.21", + "@swc/core-win32-x64-msvc": "1.15.21" }, "peerDependencies": { "@swc/helpers": ">=0.5.17" @@ -7000,9 +7053,9 @@ } }, "node_modules/node-releases": { - "version": "2.0.27", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", - "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "version": "2.0.36", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz", + "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==", "dev": true, "license": "MIT" }, @@ -7259,6 +7312,18 @@ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "license": "ISC" }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/playwright": { "version": "1.58.2", "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.2.tgz", @@ -7308,9 +7373,9 @@ } }, "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", "dev": true, "funding": [ { @@ -7511,6 +7576,30 @@ "node": ">=0.10.0" } }, + "node_modules/resolve": { + "version": "2.0.0-next.6", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.6.tgz", + "integrity": "sha512-3JmVl5hMGtJ3kMmB3zi3DL25KfkCEyy3Tw7Gmw7z5w8M9WlwoPFnIvwChzu1+cF3iaK3sp18hhPz8ANeimdJfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "node-exports-info": "^1.6.0", + "object-keys": "^1.1.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -7522,9 +7611,9 @@ } }, "node_modules/rollup": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", - "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.0.tgz", + "integrity": "sha512-yqjxruMGBQJ2gG4HtjZtAfXArHomazDHoFwFFmZZl0r7Pdo7qCIXKqKHZc8yeoMgzJJ+pO6pEEHa+V7uzWlrAQ==", "dev": true, "license": "MIT", "dependencies": { @@ -7538,31 +7627,31 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.59.0", - "@rollup/rollup-android-arm64": "4.59.0", - "@rollup/rollup-darwin-arm64": "4.59.0", - "@rollup/rollup-darwin-x64": "4.59.0", - "@rollup/rollup-freebsd-arm64": "4.59.0", - "@rollup/rollup-freebsd-x64": "4.59.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", - "@rollup/rollup-linux-arm-musleabihf": "4.59.0", - "@rollup/rollup-linux-arm64-gnu": "4.59.0", - "@rollup/rollup-linux-arm64-musl": "4.59.0", - "@rollup/rollup-linux-loong64-gnu": "4.59.0", - "@rollup/rollup-linux-loong64-musl": "4.59.0", - "@rollup/rollup-linux-ppc64-gnu": "4.59.0", - "@rollup/rollup-linux-ppc64-musl": "4.59.0", - "@rollup/rollup-linux-riscv64-gnu": "4.59.0", - "@rollup/rollup-linux-riscv64-musl": "4.59.0", - "@rollup/rollup-linux-s390x-gnu": "4.59.0", - "@rollup/rollup-linux-x64-gnu": "4.59.0", - "@rollup/rollup-linux-x64-musl": "4.59.0", - "@rollup/rollup-openbsd-x64": "4.59.0", - "@rollup/rollup-openharmony-arm64": "4.59.0", - "@rollup/rollup-win32-arm64-msvc": "4.59.0", - "@rollup/rollup-win32-ia32-msvc": "4.59.0", - "@rollup/rollup-win32-x64-gnu": "4.59.0", - "@rollup/rollup-win32-x64-msvc": "4.59.0", + "@rollup/rollup-android-arm-eabi": "4.60.0", + "@rollup/rollup-android-arm64": "4.60.0", + "@rollup/rollup-darwin-arm64": "4.60.0", + "@rollup/rollup-darwin-x64": "4.60.0", + "@rollup/rollup-freebsd-arm64": "4.60.0", + "@rollup/rollup-freebsd-x64": "4.60.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.0", + "@rollup/rollup-linux-arm-musleabihf": "4.60.0", + "@rollup/rollup-linux-arm64-gnu": "4.60.0", + "@rollup/rollup-linux-arm64-musl": "4.60.0", + "@rollup/rollup-linux-loong64-gnu": "4.60.0", + "@rollup/rollup-linux-loong64-musl": "4.60.0", + "@rollup/rollup-linux-ppc64-gnu": "4.60.0", + "@rollup/rollup-linux-ppc64-musl": "4.60.0", + "@rollup/rollup-linux-riscv64-gnu": "4.60.0", + "@rollup/rollup-linux-riscv64-musl": "4.60.0", + "@rollup/rollup-linux-s390x-gnu": "4.60.0", + "@rollup/rollup-linux-x64-gnu": "4.60.0", + "@rollup/rollup-linux-x64-musl": "4.60.0", + "@rollup/rollup-openbsd-x64": "4.60.0", + "@rollup/rollup-openharmony-arm64": "4.60.0", + "@rollup/rollup-win32-arm64-msvc": "4.60.0", + "@rollup/rollup-win32-ia32-msvc": "4.60.0", + "@rollup/rollup-win32-x64-gnu": "4.60.0", + "@rollup/rollup-win32-x64-msvc": "4.60.0", "fsevents": "~2.3.2" } }, @@ -8106,16 +8195,16 @@ } }, "node_modules/tailwindcss": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.1.tgz", - "integrity": "sha512-/tBrSQ36vCleJkAOsy9kbNTgaxvGbyOamC30PRePTQe/o1MFwEKHQk4Cn7BNGaPtjp+PuUrByJehM1hgxfq4sw==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.2.tgz", + "integrity": "sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==", "dev": true, "license": "MIT" }, "node_modules/tapable": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", - "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.1.tgz", + "integrity": "sha512-b+u3CEM6FjDHru+nhUSjDofpWSBp2rINziJWgApm72wwGasQ/wKXftZe4tI2Y5HPv6OpzXSZHOFq87H4vfsgsw==", "dev": true, "license": "MIT", "engines": { @@ -8134,9 +8223,9 @@ "license": "MIT" }, "node_modules/tinyexec": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", - "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.4.tgz", + "integrity": "sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw==", "dev": true, "license": "MIT", "engines": { @@ -8160,42 +8249,10 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/tinyglobby/node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/tinyrainbow": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz", - "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", + "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", "dev": true, "license": "MIT", "engines": { @@ -8203,22 +8260,22 @@ } }, "node_modules/tldts": { - "version": "7.0.23", - "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.23.tgz", - "integrity": "sha512-ASdhgQIBSay0R/eXggAkQ53G4nTJqTXqC2kbaBbdDwM7SkjyZyO0OaaN1/FH7U/yCeqOHDwFO5j8+Os/IS1dXw==", + "version": "7.0.27", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.27.tgz", + "integrity": "sha512-I4FZcVFcqCRuT0ph6dCDpPuO4Xgzvh+spkcTr1gK7peIvxWauoloVO0vuy1FQnijT63ss6AsHB6+OIM4aXHbPg==", "dev": true, "license": "MIT", "dependencies": { - "tldts-core": "^7.0.23" + "tldts-core": "^7.0.27" }, "bin": { "tldts": "bin/cli.js" } }, "node_modules/tldts-core": { - "version": "7.0.23", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.23.tgz", - "integrity": "sha512-0g9vrtDQLrNIiCj22HSe9d4mLVG3g5ph5DZ8zCKBr4OtrspmNB6ss7hVyzArAeE88ceZocIEGkyW1Ime7fxPtQ==", + "version": "7.0.27", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.27.tgz", + "integrity": "sha512-YQ7uPjgWUibIK6DW5lrKujGwUKhLevU4hcGbP5O6TcIUb+oTjJYJVWPS4nZsIHrEEEG6myk/oqAJUEQmpZrHsg==", "dev": true, "license": "MIT" }, @@ -8233,9 +8290,9 @@ } }, "node_modules/tough-cookie": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.0.tgz", - "integrity": "sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.1.tgz", + "integrity": "sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -8259,9 +8316,9 @@ } }, "node_modules/ts-api-utils": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", - "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", "dev": true, "license": "MIT", "engines": { @@ -8403,9 +8460,9 @@ } }, "node_modules/undici": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.4.tgz", - "integrity": "sha512-BM/JzwwaRXxrLdElV2Uo6cTLEjhSb3WXboncJamZ15NgUURmvlXvxa6xkwIOILIjPNo9i8ku136ZvWV0Uly8+w==", + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.5.tgz", + "integrity": "sha512-3IWdCpjgxp15CbJnsi/Y9TCDE7HWVN19j1hmzVhoAkY/+CJx449tVxT5wZc1Gwg8J+P0LWvzlBzxYRnHJ+1i7Q==", "dev": true, "license": "MIT", "engines": { @@ -8557,24 +8614,6 @@ } } }, - "node_modules/vite/node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, "node_modules/vite/node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -8590,20 +8629,6 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/vite/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/vitest": { "version": "4.0.18", "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.18.tgz", @@ -8683,19 +8708,6 @@ } } }, - "node_modules/vitest/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/w3c-xmlserializer": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", @@ -8938,9 +8950,9 @@ } }, "node_modules/zustand": { - "version": "5.0.11", - "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.11.tgz", - "integrity": "sha512-fdZY+dk7zn/vbWNCYmzZULHRrss0jx5pPFiOuMZ/5HJN6Yv3u+1Wswy/4MpZEkEGhtNH+pwxZB8OKgUBPzYAGg==", + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.12.tgz", + "integrity": "sha512-i77ae3aZq4dhMlRhJVCYgMLKuSiZAaUPAct2AksxQ+gOtimhGMdXljRT21P5BNpeT4kXlLIckvkPM029OljD7g==", "license": "MIT", "engines": { "node": ">=12.20.0" diff --git a/package.json b/package.json index e8ba594..b0990bb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jmap-webmail", - "version": "1.3.3", + "version": "1.4.0", "description": "A modern JMAP webmail client built for Stalwart Mail Server", "author": "Matthieu MALVACHE ", "license": "MIT", @@ -57,15 +57,16 @@ "@typescript-eslint/eslint-plugin": "^8.49.0", "@typescript-eslint/parser": "^8.49.0", "@vitejs/plugin-react": "^5.1.2", - "@vitest/ui": "^4.0.16", + "@vitest/ui": "^4.0.18", "eslint": "^9.39.2", "eslint-plugin-react": "^7.37.5", "eslint-plugin-react-hooks": "^7.0.1", + "flatted": "^3.4.2", "globals": "^17.0.0", "husky": "^9.1.7", "jsdom": "^28.1.0", "tailwindcss": "^4.1.17", "typescript": "^5.9.3", - "vitest": "^4.0.16" + "vitest": "^4.0.18" } } diff --git a/stores/contact-store.ts b/stores/contact-store.ts index cc84df2..3aed331 100644 --- a/stores/contact-store.ts +++ b/stores/contact-store.ts @@ -92,8 +92,7 @@ export const useContactStore = create()( try { const contacts = await client.getContacts(); set({ contacts, isLoading: false }); - } catch (error) { - console.error('Failed to fetch contacts:', error); + } catch { set({ error: 'Failed to fetch contacts', isLoading: false }); } }, @@ -102,8 +101,7 @@ export const useContactStore = create()( try { const addressBooks = await client.getAddressBooks(); set({ addressBooks }); - } catch (error) { - console.error('Failed to fetch address books:', error); + } catch { set({ error: 'Failed to fetch address books' }); } }, @@ -351,8 +349,7 @@ export const useContactStore = create()( for (const id of ids) { try { await client.deleteContact(id); - } catch (error) { - console.error(`Failed to delete contact ${id}:`, error); + } catch { deletedIds.delete(id); } } @@ -391,8 +388,8 @@ export const useContactStore = create()( set((state) => ({ contacts: [...state.contacts, localContact] })); } imported++; - } catch (error) { - console.error('Failed to import contact:', error); + } catch { + continue; } } diff --git a/stores/email-store.ts b/stores/email-store.ts index 317062b..33f67dd 100644 --- a/stores/email-store.ts +++ b/stores/email-store.ts @@ -1,5 +1,5 @@ import { create } from "zustand"; -import { Email, Mailbox, StateChange } from "@/lib/jmap/types"; +import { Email, Mailbox, StateChange, ThreadGroup } from "@/lib/jmap/types"; import { JMAPClient } from "@/lib/jmap/client"; import { useSettingsStore } from "@/stores/settings-store"; import { useCalendarStore } from "@/stores/calendar-store"; @@ -18,6 +18,7 @@ interface EmailStore { quota: { used: number; total: number } | null; processingReadStatus: Set; // Track emails being marked as read/unread selectedEmailIds: Set; // Track selected emails for batch operations + lastSelectedIndex: number | null; hasMoreEmails: boolean; // Track if more emails are available to load totalEmails: number; // Total number of emails in the current mailbox/query isPushConnected: boolean; // Track if push notifications are connected @@ -43,9 +44,11 @@ interface EmailStore { setError: (error: string | null) => void; setSearchQuery: (query: string) => void; setQuota: (quota: { used: number; total: number } | null) => void; - toggleEmailSelection: (emailId: string) => void; - selectAllEmails: () => void; + toggleEmailSelection: (emailId: string, groupIndex?: number) => void; + selectAllEmails: (threadGroups?: ThreadGroup[]) => void; clearSelection: () => void; + selectRange: (fromIndex: number, toIndex: number, threadGroups: ThreadGroup[]) => void; + selectByFilter: (filter: string, threadGroups: ThreadGroup[]) => void; // JMAP operations fetchMailboxes: (client: JMAPClient) => Promise; @@ -96,6 +99,11 @@ interface EmailStore { // Empty folder emptyFolder: (client: JMAPClient, mailboxId: string, onProgress?: (deleted: number, total: number) => void) => Promise; + createMailbox: (client: JMAPClient, name: string, parentId?: string) => Promise; + renameMailbox: (client: JMAPClient, mailboxId: string, newName: string) => Promise; + moveMailbox: (client: JMAPClient, mailboxId: string, newParentId: string | null) => Promise; + deleteMailbox: (client: JMAPClient, mailboxId: string) => Promise; + // Mock data for demo loadMockData: () => void; } @@ -113,6 +121,7 @@ export const useEmailStore = create((set, get) => ({ quota: null, processingReadStatus: new Set(), selectedEmailIds: new Set(), + lastSelectedIndex: null, hasMoreEmails: false, totalEmails: 0, isPushConnected: false, @@ -142,6 +151,7 @@ export const useEmailStore = create((set, get) => ({ selectedMailbox: mailboxId, selectedEmail: null, selectedEmailIds: new Set(), + lastSelectedIndex: null, expandedThreadIds: new Set(), threadEmailsCache: new Map(), isLoadingThread: null, @@ -152,7 +162,7 @@ export const useEmailStore = create((set, get) => ({ setSearchQuery: (query) => set({ searchQuery: query }), setQuota: (quota) => set({ quota }), - toggleEmailSelection: (emailId) => { + toggleEmailSelection: (emailId, groupIndex) => { const { selectedEmailIds } = get(); const newSelection = new Set(selectedEmailIds); if (newSelection.has(emailId)) { @@ -160,22 +170,73 @@ export const useEmailStore = create((set, get) => ({ } else { newSelection.add(emailId); } - set({ selectedEmailIds: newSelection }); + set({ + selectedEmailIds: newSelection, + lastSelectedIndex: groupIndex ?? get().lastSelectedIndex, + }); }, - selectAllEmails: () => { - const { emails } = get(); - const allIds = new Set(emails.map(e => e.id)); - set({ selectedEmailIds: allIds }); + selectAllEmails: (threadGroups) => { + if (threadGroups) { + const allIds = new Set(threadGroups.map(g => g.latestEmail.id)); + set({ selectedEmailIds: allIds, lastSelectedIndex: null }); + } else { + const { emails } = get(); + const allIds = new Set(emails.map(e => e.id)); + set({ selectedEmailIds: allIds }); + } }, clearSelection: () => { - set({ selectedEmailIds: new Set() }); + set({ selectedEmailIds: new Set(), lastSelectedIndex: null }); + }, + + selectRange: (fromIndex, toIndex, threadGroups) => { + const start = Math.min(fromIndex, toIndex); + const end = Math.max(fromIndex, toIndex); + const { selectedEmailIds } = get(); + const newSelection = new Set(selectedEmailIds); + for (let i = start; i <= end; i++) { + const group = threadGroups[i]; + if (group) { + newSelection.add(group.latestEmail.id); + } + } + set({ selectedEmailIds: newSelection, lastSelectedIndex: toIndex }); + }, + + selectByFilter: (filter, threadGroups) => { + if (filter === 'none') { + set({ selectedEmailIds: new Set(), lastSelectedIndex: null }); + return; + } + if (filter === 'all') { + const allIds = new Set(threadGroups.map(g => g.latestEmail.id)); + set({ selectedEmailIds: allIds, lastSelectedIndex: null }); + return; + } + const newSelection = new Set(); + for (const group of threadGroups) { + const email = group.latestEmail; + const seen = !!email.keywords?.$seen; + const flagged = !!email.keywords?.$flagged; + const match = + (filter === 'read' && seen) || + (filter === 'unread' && !seen) || + (filter === 'starred' && flagged) || + (filter === 'unstarred' && !flagged); + if (match) { + newSelection.add(email.id); + } + } + set({ selectedEmailIds: newSelection, lastSelectedIndex: null }); }, // JMAP operations fetchMailboxes: async (client) => { - set({ isLoading: true, error: null }); + if (get().mailboxes.length === 0) { + set({ isLoading: true, error: null }); + } try { const mailboxes = await client.getAllMailboxes(); @@ -224,7 +285,6 @@ export const useEmailStore = create((set, get) => ({ isLoading: false }); } catch (error) { - console.error('Failed to fetch emails:', error); set({ error: error instanceof Error ? error.message : "Failed to fetch emails", isLoading: false, @@ -283,7 +343,6 @@ export const useEmailStore = create((set, get) => ({ isLoadingMore: false }); } catch (error) { - console.error('Failed to load more emails:', error); set({ error: error instanceof Error ? error.message : "Failed to load more emails", isLoadingMore: false @@ -325,15 +384,12 @@ export const useEmailStore = create((set, get) => ({ }, sendEmail: async (client, to, subject, body, cc, bcc, identityId, fromEmail, draftId, fromName) => { - set({ isLoading: true, error: null }); + set({ error: null }); try { await client.sendEmail(to, subject, body, cc, bcc, identityId, fromEmail, draftId, fromName); - // Refresh handled by UI layer for immediate feedback - set({ isLoading: false }); } catch (error) { set({ error: error instanceof Error ? error.message : "Failed to send email", - isLoading: false }); throw error; } @@ -727,7 +783,7 @@ export const useEmailStore = create((set, get) => ({ const { selectedEmailIds, emails, mailboxes } = get(); if (selectedEmailIds.size === 0) return; - set({ isLoading: true, error: null }); + set({ error: null }); try { const emailIdsArray = Array.from(selectedEmailIds); await client.batchMarkAsRead(emailIdsArray, read); @@ -763,12 +819,10 @@ export const useEmailStore = create((set, get) => ({ emails: updatedEmails, mailboxes: updatedMailboxes, selectedEmailIds: new Set(), - isLoading: false }); } catch (error) { set({ error: error instanceof Error ? error.message : "Failed to update emails", - isLoading: false }); } }, @@ -777,7 +831,7 @@ export const useEmailStore = create((set, get) => ({ const { selectedEmailIds, emails, mailboxes } = get(); if (selectedEmailIds.size === 0) return; - set({ isLoading: true, error: null }); + set({ error: null }); try { const emailIdsArray = Array.from(selectedEmailIds); await client.batchDeleteEmails(emailIdsArray); @@ -814,12 +868,10 @@ export const useEmailStore = create((set, get) => ({ mailboxes: updatedMailboxes, selectedEmailIds: new Set(), selectedEmail: null, - isLoading: false }); } catch (error) { set({ error: error instanceof Error ? error.message : "Failed to delete emails", - isLoading: false }); } }, @@ -828,7 +880,7 @@ export const useEmailStore = create((set, get) => ({ const { selectedEmailIds, emails } = get(); if (selectedEmailIds.size === 0) return; - set({ isLoading: true, error: null }); + set({ error: null }); try { const emailIdsArray = Array.from(selectedEmailIds); await client.batchMoveEmails(emailIdsArray, toMailboxId); @@ -839,15 +891,13 @@ export const useEmailStore = create((set, get) => ({ set({ emails: remainingEmails, selectedEmailIds: new Set(), - isLoading: false }); - // Refresh emails to get updated list - await get().fetchEmails(client, get().selectedMailbox); + // Silent refresh to sync with server + await get().refreshCurrentMailbox(client); } catch (error) { set({ error: error instanceof Error ? error.message : "Failed to move emails", - isLoading: false }); } }, @@ -867,21 +917,16 @@ export const useEmailStore = create((set, get) => ({ accountId: currentMailbox.accountId, }); - try { - await client.markAsSpam(emailId, currentMailbox.accountId); + await client.markAsSpam(emailId, currentMailbox.accountId); - set(state => ({ - emails: state.emails.filter(e => e.id !== emailId), - selectedEmail: state.selectedEmail?.id === emailId ? null : state.selectedEmail, - })); + set(state => ({ + emails: state.emails.filter(e => e.id !== emailId), + selectedEmail: state.selectedEmail?.id === emailId ? null : state.selectedEmail, + })); - const currentIndex = emails.findIndex(e => e.id === emailId); - if (currentIndex >= 0 && currentIndex < emails.length - 1) { - set({ selectedEmail: emails[currentIndex + 1] }); - } - } catch (error) { - console.error('Failed to mark as spam:', error); - throw error; + const currentIndex = emails.findIndex(e => e.id === emailId); + if (currentIndex >= 0 && currentIndex < emails.length - 1) { + set({ selectedEmail: emails[currentIndex + 1] }); } }, @@ -917,13 +962,8 @@ export const useEmailStore = create((set, get) => ({ targetMailboxId = inboxMailbox.originalId || inboxMailbox.id; } - try { - await client.undoSpam(emailId, targetMailboxId, accountId); - await get().fetchEmails(client, selectedMailbox); - } catch (error) { - console.error('Failed to restore email:', error); - throw error; - } + await client.undoSpam(emailId, targetMailboxId, accountId); + await get().fetchEmails(client, selectedMailbox); }, batchMarkAsSpam: async (client, emailIds) => { @@ -932,20 +972,15 @@ export const useEmailStore = create((set, get) => ({ const currentMailbox = mailboxes.find(m => m.id === selectedMailbox); if (!currentMailbox) return; - try { - for (const emailId of emailIds) { - await client.markAsSpam(emailId, currentMailbox.accountId); - } - - set(state => ({ - emails: state.emails.filter(e => !emailIds.includes(e.id)), - selectedEmail: emailIds.includes(state.selectedEmail?.id || '') ? null : state.selectedEmail, - selectedEmailIds: new Set(), - })); - } catch (error) { - console.error('Failed to batch mark as spam:', error); - throw error; + for (const emailId of emailIds) { + await client.markAsSpam(emailId, currentMailbox.accountId); } + + set(state => ({ + emails: state.emails.filter(e => !emailIds.includes(e.id)), + selectedEmail: emailIds.includes(state.selectedEmail?.id || '') ? null : state.selectedEmail, + selectedEmailIds: new Set(), + })); }, batchUndoSpam: async (client: JMAPClient, emailIds: string[]) => { @@ -964,20 +999,15 @@ export const useEmailStore = create((set, get) => ({ throw new Error('Inbox not found'); } - try { - for (const emailId of emailIds) { - await client.undoSpam(emailId, inboxMailbox.originalId || inboxMailbox.id, accountId); - } - - set(state => ({ - emails: state.emails.filter(e => !emailIds.includes(e.id)), - selectedEmail: emailIds.includes(state.selectedEmail?.id || '') ? null : state.selectedEmail, - selectedEmailIds: new Set(), - })); - } catch (error) { - console.error('Failed to batch restore emails:', error); - throw error; + for (const emailId of emailIds) { + await client.undoSpam(emailId, inboxMailbox.originalId || inboxMailbox.id, accountId); } + + set(state => ({ + emails: state.emails.filter(e => !emailIds.includes(e.id)), + selectedEmail: emailIds.includes(state.selectedEmail?.id || '') ? null : state.selectedEmail, + selectedEmailIds: new Set(), + })); }, // Push notification handlers @@ -1025,13 +1055,10 @@ export const useEmailStore = create((set, get) => ({ const { useFilterStore } = await import('./filter-store'); const filterStore = useFilterStore.getState(); if (filterStore.isSupported) { - filterStore.fetchFilters(client).catch((err) => { - console.error('Failed to refresh filters:', err); - }); + filterStore.fetchFilters(client).catch(() => {}); } } } catch (error) { - console.error('Failed to handle state change:', error); set({ error: error instanceof Error ? error.message : "Failed to handle push notification" }); @@ -1088,10 +1115,7 @@ export const useEmailStore = create((set, get) => ({ totalEmails: result.total, }); } - } catch (error) { - console.error('Failed to refresh current mailbox:', error); - // Don't set error state for background refreshes to avoid disrupting the UI - } + } catch { /* silent: push refresh is best-effort */ } }, handleNewEmailNotification: (email) => { @@ -1148,8 +1172,7 @@ export const useEmailStore = create((set, get) => ({ }); return emails; - } catch (error) { - console.error('Failed to fetch thread emails:', error); + } catch { set({ isLoadingThread: null }); return []; } @@ -1173,9 +1196,7 @@ export const useEmailStore = create((set, get) => ({ const tags = ["red", "orange", "yellow", "green", "blue", "purple", "pink"]; const tagCounts = await client.queryTagCounts(tags); set({ tagCounts }); - } catch (error) { - console.error("Failed to fetch tag counts:", error); - } + } catch { /* silent: tag counts are non-critical */ } }, emptyFolder: async (client, mailboxId, onProgress) => { @@ -1215,6 +1236,159 @@ export const useEmailStore = create((set, get) => ({ } }, + createMailbox: async (client, name, parentId) => { + const tempId = `temp-${Date.now()}`; + const mailboxes = get().mailboxes; + + const tempMailbox: Mailbox = { + id: tempId, + name, + parentId: parentId || undefined, + sortOrder: 999, + totalEmails: 0, + unreadEmails: 0, + totalThreads: 0, + unreadThreads: 0, + myRights: { + mayReadItems: true, mayAddItems: true, mayRemoveItems: true, + maySetSeen: true, maySetKeywords: true, mayCreateChild: true, + mayRename: true, mayDelete: true, maySubmit: false, + }, + isSubscribed: true, + }; + + set({ mailboxes: [...mailboxes, tempMailbox] }); + + try { + const realId = await client.createMailbox(name, parentId); + + set((state) => { + const updated = state.mailboxes.map(mb => + mb.id === tempId ? { ...mb, id: realId } : mb + ); + const newState: Partial = { mailboxes: updated }; + if (state.selectedMailbox === tempId) { + newState.selectedMailbox = realId; + } + return newState; + }); + + return realId; + } catch (error) { + set({ mailboxes: get().mailboxes.filter(mb => mb.id !== tempId) }); + throw error; + } + }, + + renameMailbox: async (client, mailboxId, newName) => { + const mailboxes = get().mailboxes; + const mailbox = mailboxes.find(mb => mb.id === mailboxId); + if (!mailbox) return false; + + const previousName = mailbox.name; + const jmapId = mailbox.originalId || mailboxId; + + set({ + mailboxes: mailboxes.map(mb => + mb.id === mailboxId ? { ...mb, name: newName } : mb + ), + }); + + try { + await client.updateMailbox(jmapId, { name: newName }); + return true; + } catch (error) { + set({ + mailboxes: get().mailboxes.map(mb => + mb.id === mailboxId ? { ...mb, name: previousName } : mb + ), + }); + throw error; + } + }, + + moveMailbox: async (client, mailboxId, newParentId) => { + const mailboxes = get().mailboxes; + const mailbox = mailboxes.find(mb => mb.id === mailboxId); + if (!mailbox) return false; + + const previousParentId = mailbox.parentId; + const jmapId = mailbox.originalId || mailboxId; + + set({ + mailboxes: mailboxes.map(mb => + mb.id === mailboxId ? { ...mb, parentId: newParentId || undefined } : mb + ), + }); + + try { + await client.updateMailbox(jmapId, { parentId: newParentId }); + return true; + } catch (error) { + set({ + mailboxes: get().mailboxes.map(mb => + mb.id === mailboxId ? { ...mb, parentId: previousParentId } : mb + ), + }); + throw error; + } + }, + + deleteMailbox: async (client, mailboxId) => { + const mailboxes = get().mailboxes; + const mailbox = mailboxes.find(mb => mb.id === mailboxId); + if (!mailbox) return false; + + if (get().selectedMailbox === mailboxId) { + const inbox = mailboxes.find(mb => mb.role === 'inbox'); + if (inbox) set({ selectedMailbox: inbox.id }); + } + + const collectDescendants = (parentId: string): Mailbox[] => { + const children = mailboxes.filter(mb => mb.parentId === parentId); + const descendants: Mailbox[] = []; + for (const child of children) { + descendants.push(...collectDescendants(child.id)); + descendants.push(child); + } + return descendants; + }; + + const descendants = collectDescendants(mailboxId); + const allToDelete = [...descendants, mailbox]; + const allIds = new Set(allToDelete.map(mb => mb.id)); + + const trashMailbox = mailboxes.find(mb => mb.role === 'trash'); + const trashId = trashMailbox?.originalId || trashMailbox?.id; + + set({ mailboxes: mailboxes.filter(mb => !allIds.has(mb.id)) }); + + try { + for (const mb of allToDelete) { + const jmapId = mb.originalId || mb.id; + + if (trashId && mb.totalEmails > 0) { + let position = 0; + while (true) { + const batch = await client.queryMailboxEmailIds(jmapId, 500, position); + if (batch.ids.length === 0) break; + await client.batchMoveEmails(batch.ids, trashId); + if (batch.ids.length < 500) break; + position = 0; + } + } + + await client.destroyMailbox(jmapId); + } + + await get().fetchMailboxes(client); + return true; + } catch (error) { + await get().fetchMailboxes(client); + throw error; + } + }, + loadMockData: () => { const mockEmails: Email[] = [ { diff --git a/stores/settings-store.ts b/stores/settings-store.ts index 0509ff7..d56c942 100644 --- a/stores/settings-store.ts +++ b/stores/settings-store.ts @@ -187,8 +187,7 @@ export const useSettingsStore = create()( applySidebarWidth(get().sidebarWidth); return true; - } catch (error) { - console.error('Failed to import settings:', error); + } catch { return false; } },