diff --git a/.env.example b/.env.example index 41fd69485..f875e3e34 100644 --- a/.env.example +++ b/.env.example @@ -30,6 +30,10 @@ GOOGLE_CLIENT_SECRET="" GITHUB_CLIENT_ID="" GITHUB_CLIENT_SECRET="" +# Social Auth (LinkedIn, optional) +LINKEDIN_CLIENT_ID="" +LINKEDIN_CLIENT_SECRET="" + # Custom OAuth Provider (optional) OAUTH_PROVIDER_NAME="" OAUTH_CLIENT_ID="" diff --git a/AGENTS.md b/AGENTS.md index 12a582364..2cc7fcffb 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,5 +1,3 @@ -# AGENTS.md - ## Overview Reactive Resume is a single-package full-stack TypeScript app (not a monorepo) built with [TanStack Start](https://tanstack.com/start/latest/docs/framework/react/overview) (React, Vite, Nitro). It serves both frontend and API on port 3000. @@ -130,4 +128,4 @@ Copy `.env.example` to `.env` if not present. Key notes for local dev: ## Review Checklist for Agents - [ ] Run `vp install` after pulling remote changes and before getting started. -- [ ] Run `vp check` and `vp test` to validate changes. +- [ ] Run `pnpm lint:fix`, `pnpm fmt:fix`, `pnpm typecheck` and `vp test` to validate changes. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 120000 index 000000000..47dc3e3d8 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file diff --git a/docs/changelog/index.mdx b/docs/changelog/index.mdx index 44be9b501..2c552021d 100644 --- a/docs/changelog/index.mdx +++ b/docs/changelog/index.mdx @@ -4,6 +4,37 @@ description: "List of all notable changes and updates to Reactive Resume" rss: true --- + +## Features & Improvements +- Added **LinkedIn sign-in** support for self-hosted instances, including sign-in on the auth page and account linking in settings. +- Improved the sign-in experience by showing loading placeholders while social login providers are being fetched. +- Resume builder panel sizes are now persisted more reliably, making the layout feel more consistent between sessions. +- Added clearer labels for resume sorting and filtering controls on the dashboard. + +## Fixes +- Improved autosave reliability in the resume builder: + - Unsaved edits are flushed before page unload. + - You now get a clear persistent warning if changes fail to save (for example, due to network issues). +- Strengthened authentication defaults by increasing the minimum password length requirement to 8 characters. +- Improved reliability around resume deletion and server-side error handling for create/update operations. +- Fixed a rare language mix-up during printing/export when multiple print jobs run at the same time, so each PDF/screenshot now reliably uses the correct locale. +- Improved print stability under heavy usage by reusing in-progress PDF and screenshot generation requests for the same resume instead of starting duplicate jobs. +- Hardened printer token signing and token/session/API-key verification paths for better security. +- Improved resilience in AI tailoring output parsing and JSON Resume import normalization. +- Printer service now provides descriptive error messages ("Failed to generate PDF" / "Failed to capture screenshot") instead of generic internal server errors. +- Job descriptions in the job detail sheet now render HTML formatting (headings, lists, bold text) instead of showing raw tags. +- Fixed the "Tailor Resume" button being incorrectly disabled when AI is not configured — the dialog already supports plain resume duplication as a fallback. +- Fixed the tailor resume flow to open the resume builder in a new tab while keeping the job listing open for reference, instead of navigating away and auto-opening the application page. +- Apply option links in the job detail sheet are now validated before rendering, preventing malformed or potentially unsafe URLs. +- Improved job card loading skeletons to match the actual card layout (logo, title, badges) for a smoother loading experience. +- Centered the "Configure Job Search" empty state on the page. +- Extracted the `JobCard` component into its own file for better code organization. + +## Docs & Localization +- Updated self-hosting and environment variable documentation to include LinkedIn OAuth configuration. +- Synced translations across locales for the latest authentication and save-status messaging. + + ## Features - Implemented OAuth 2.1 support for MCP authentication, thanks to @5queezer. [#2829](https://github.com/amruthpillai/reactive-resume/pull/2829) diff --git a/docs/getting-started/quickstart.mdx b/docs/getting-started/quickstart.mdx index 823d250be..53db7147c 100644 --- a/docs/getting-started/quickstart.mdx +++ b/docs/getting-started/quickstart.mdx @@ -138,6 +138,8 @@ Here's a complete list of environment variables you can configure: | `GOOGLE_CLIENT_SECRET` | Google OAuth Client Secret | — | | `GITHUB_CLIENT_ID` | GitHub OAuth Client ID | — | | `GITHUB_CLIENT_SECRET` | GitHub OAuth Client Secret | — | +| `LINKEDIN_CLIENT_ID` | LinkedIn OAuth Client ID | — | +| `LINKEDIN_CLIENT_SECRET` | LinkedIn OAuth Client Secret | — | | `OAUTH_PROVIDER_NAME` | Custom OAuth Provider Name | — | | `OAUTH_CLIENT_ID` | Custom OAuth Client ID | — | | `OAUTH_CLIENT_SECRET` | Custom OAuth Client Secret | — | diff --git a/docs/self-hosting/docker.mdx b/docs/self-hosting/docker.mdx index a90942d61..1a8504e6f 100644 --- a/docs/self-hosting/docker.mdx +++ b/docs/self-hosting/docker.mdx @@ -73,6 +73,10 @@ GOOGLE_CLIENT_SECRET="" GITHUB_CLIENT_ID="" GITHUB_CLIENT_SECRET="" +# Social Auth (LinkedIn, optional) +LINKEDIN_CLIENT_ID="" +LINKEDIN_CLIENT_SECRET="" + # Custom OAuth Provider OAUTH_PROVIDER_NAME="" OAUTH_CLIENT_ID="" @@ -338,6 +342,8 @@ openssl rand -hex 32 **`GITHUB_CLIENT_ID`** / **`GITHUB_CLIENT_SECRET`** (optional): Enables GitHub sign-in. + **`LINKEDIN_CLIENT_ID`** / **`LINKEDIN_CLIENT_SECRET`** (optional): Enables LinkedIn sign-in. + **Custom OAuth provider** (optional): - **`OAUTH_PROVIDER_NAME`**: Display name in the UI - **`OAUTH_CLIENT_ID`** / **`OAUTH_CLIENT_SECRET`**: Required for any custom OAuth provider @@ -375,7 +381,7 @@ openssl rand -hex 32 - **`FLAG_DEBUG_PRINTER`**: Bypasses the printer-only access restriction (useful when debugging `/printer/{resumeId}`). Recommended: keep `"false"` in production. - **`FLAG_DISABLE_SIGNUPS`**: Disables new signups (web app and server). Useful for private instances. - - **`FLAG_DISABLE_EMAIL_AUTH`**: Disables email/password login entirely. Also disables email verification, forgot password, and reset password flows. Users can still sign up via social auth (Google/GitHub/Custom OAuth), unless FLAG_DISABLE_SIGNUPS is also set to true. Useful when only SSO is required. + - **`FLAG_DISABLE_EMAIL_AUTH`**: Disables email/password login entirely. Also disables email verification, forgot password, and reset password flows. Users can still sign up via social auth (Google/GitHub/LinkedIn/Custom OAuth), unless FLAG_DISABLE_SIGNUPS is also set to true. Useful when only SSO is required. - **`FLAG_DISABLE_IMAGE_PROCESSING`**: Disables image processing. This is useful if you are using a machine with limited resources, like a Raspberry Pi. diff --git a/docs/self-hosting/examples.mdx b/docs/self-hosting/examples.mdx index cd7fc05eb..1ef9dd95a 100644 --- a/docs/self-hosting/examples.mdx +++ b/docs/self-hosting/examples.mdx @@ -405,6 +405,8 @@ services: - GOOGLE_CLIENT_SECRET=$GOOGLE_CLIENT_SECRET - GITHUB_CLIENT_ID=$GITHUB_CLIENT_ID - GITHUB_CLIENT_SECRET=$GITHUB_CLIENT_SECRET + - LINKEDIN_CLIENT_ID=$LINKEDIN_CLIENT_ID + - LINKEDIN_CLIENT_SECRET=$LINKEDIN_CLIENT_SECRET - SMTP_HOST=$SMTP_HOST - SMTP_PORT=$SMTP_PORT - SMTP_USER=$SMTP_USER diff --git a/docs/self-hosting/sso.mdx b/docs/self-hosting/sso.mdx index a314ab0aa..1e99d4a59 100644 --- a/docs/self-hosting/sso.mdx +++ b/docs/self-hosting/sso.mdx @@ -253,6 +253,8 @@ AUTH_SECRET="your-32-byte-hex-secret" # GOOGLE_CLIENT_SECRET="" # GITHUB_CLIENT_ID="" # GITHUB_CLIENT_SECRET="" +# LINKEDIN_CLIENT_ID="" +# LINKEDIN_CLIENT_SECRET="" # Custom OAuth Provider (e.g., Authentik) OAUTH_PROVIDER_NAME="Company SSO" diff --git a/locales/af-ZA.po b/locales/af-ZA.po index eba436505..2d26b851f 100644 --- a/locales/af-ZA.po +++ b/locales/af-ZA.po @@ -3186,6 +3186,10 @@ msgstr "Jou data word plaaslik gestoor" msgid "Your data is stored securely and is never shared with third parties. You can also self-host Reactive Resume on your own servers for complete control over your data." msgstr "Jou data word veilig gestoor en nooit met derde partye gedeel nie. Jy kan ook Reactive Resume self-host op jou eie bedieners vir volledige beheer oor jou data." +#: src/components/resume/store/resume.ts +msgid "Your latest changes could not be saved. Please make sure you are connected to the internet and try again." +msgstr "" + #: src/routes/auth/reset-password.tsx msgid "Your password has been reset successfully. You can now sign in with your new password." msgstr "Jou wagwoord is suksesvol herstel. Jy kan nou met jou nuwe wagwoord aanmeld." @@ -3244,4 +3248,3 @@ msgstr "Zoem uit" #: src/utils/locale.ts msgid "Zulu" msgstr "Zoeloe" - diff --git a/locales/am-ET.po b/locales/am-ET.po index 62b038182..cc0882b69 100644 --- a/locales/am-ET.po +++ b/locales/am-ET.po @@ -3186,6 +3186,10 @@ msgstr "መረጃዎ በአካባቢዎ መሣሪያ ላይ ይቀመጣል" msgid "Your data is stored securely and is never shared with third parties. You can also self-host Reactive Resume on your own servers for complete control over your data." msgstr "መረጃዎ በደህና ይከማቻል እና ከሶስተኛ ወገኖች ጋር አይተካፈልም። ደግሞም በራስዎ ሰርቨር ላይ Reactive Resumeን በመጫን በመረጃዎ ላይ ሙሉ ቁጥጥር ማግኘት ትችላላችሁ።" +#: src/components/resume/store/resume.ts +msgid "Your latest changes could not be saved. Please make sure you are connected to the internet and try again." +msgstr "" + #: src/routes/auth/reset-password.tsx msgid "Your password has been reset successfully. You can now sign in with your new password." msgstr "የይለፍ ቃልዎ በተሳካ ሁኔታ ዳግም ተጀምሯል። አሁን በአዲሱ የይለፍ ቃል መግባት ትችላላችሁ።" @@ -3244,4 +3248,3 @@ msgstr "አጉር" #: src/utils/locale.ts msgid "Zulu" msgstr "ዙሉ" - diff --git a/locales/ar-SA.po b/locales/ar-SA.po index f28d16cdc..256c80636 100644 --- a/locales/ar-SA.po +++ b/locales/ar-SA.po @@ -3186,6 +3186,10 @@ msgstr "تُخزَّن بياناتك محليًا" msgid "Your data is stored securely and is never shared with third parties. You can also self-host Reactive Resume on your own servers for complete control over your data." msgstr "تُخزَّن بياناتك بأمان ولا تتم مشاركتها أبدًا مع أطراف ثالثة. يمكنك أيضًا استضافة Reactive Resume ذاتيًا على خوادمك الخاصة للتحكم الكامل في بياناتك." +#: src/components/resume/store/resume.ts +msgid "Your latest changes could not be saved. Please make sure you are connected to the internet and try again." +msgstr "" + #: src/routes/auth/reset-password.tsx msgid "Your password has been reset successfully. You can now sign in with your new password." msgstr "تمت إعادة تعيين كلمة المرور الخاصة بك بنجاح. يمكنك الآن تسجيل الدخول باستخدام كلمة المرور الجديدة." @@ -3244,4 +3248,3 @@ msgstr "تصغير" #: src/utils/locale.ts msgid "Zulu" msgstr "الزولو" - diff --git a/locales/az-AZ.po b/locales/az-AZ.po index 0ff3db41d..f830d1ec5 100644 --- a/locales/az-AZ.po +++ b/locales/az-AZ.po @@ -3186,6 +3186,10 @@ msgstr "Məlumatlarınız lokal olaraq saxlanılır" msgid "Your data is stored securely and is never shared with third parties. You can also self-host Reactive Resume on your own servers for complete control over your data." msgstr "Məlumatlarınız təhlükəsiz şəkildə saxlanılır və heç vaxt üçüncü tərəflərlə paylaşılmır. Məlumatlarınıza tam nəzarət üçün Reactive Resume‑ni öz serverlərinizdə də host edə bilərsiniz." +#: src/components/resume/store/resume.ts +msgid "Your latest changes could not be saved. Please make sure you are connected to the internet and try again." +msgstr "" + #: src/routes/auth/reset-password.tsx msgid "Your password has been reset successfully. You can now sign in with your new password." msgstr "Parolunuz uğurla sıfırlandı. İndi yeni parolunuzla daxil ola bilərsiniz." @@ -3244,4 +3248,3 @@ msgstr "Uzaqlaşdır" #: src/utils/locale.ts msgid "Zulu" msgstr "Zulu" - diff --git a/locales/bg-BG.po b/locales/bg-BG.po index 7413a0435..87354b2d2 100644 --- a/locales/bg-BG.po +++ b/locales/bg-BG.po @@ -3186,6 +3186,10 @@ msgstr "Вашите данни се съхраняват локално" msgid "Your data is stored securely and is never shared with third parties. You can also self-host Reactive Resume on your own servers for complete control over your data." msgstr "Вашите данни се съхраняват сигурно и никога не се споделят с трети страни. Можете също да хоствате Reactive Resume самостоятелно на свои сървъри за пълен контрол върху данните си." +#: src/components/resume/store/resume.ts +msgid "Your latest changes could not be saved. Please make sure you are connected to the internet and try again." +msgstr "" + #: src/routes/auth/reset-password.tsx msgid "Your password has been reset successfully. You can now sign in with your new password." msgstr "Паролата ви беше нулирана успешно. Сега можете да влезете с новата си парола." @@ -3244,4 +3248,3 @@ msgstr "Намаляване" #: src/utils/locale.ts msgid "Zulu" msgstr "Зулуски" - diff --git a/locales/bn-BD.po b/locales/bn-BD.po index 534716d6b..1d58b545f 100644 --- a/locales/bn-BD.po +++ b/locales/bn-BD.po @@ -3186,6 +3186,10 @@ msgstr "আপনার ডেটা স্থানীয়ভাবে সং msgid "Your data is stored securely and is never shared with third parties. You can also self-host Reactive Resume on your own servers for complete control over your data." msgstr "আপনার ডেটা নিরাপদভাবে সংরক্ষিত হয় এবং কখনোই তৃতীয় পক্ষের সঙ্গে শেয়ার করা হয় না। চাইলে আপনি নিজের সার্ভারে Reactive Resume নিজে হোস্ট করেও ডেটার পূর্ণ নিয়ন্ত্রণ রাখতে পারেন।" +#: src/components/resume/store/resume.ts +msgid "Your latest changes could not be saved. Please make sure you are connected to the internet and try again." +msgstr "" + #: src/routes/auth/reset-password.tsx msgid "Your password has been reset successfully. You can now sign in with your new password." msgstr "আপনার পাসওয়ার্ড সফলভাবে রিসেট হয়েছে। এখন আপনি নতুন পাসওয়ার্ড দিয়ে সাইন ইন করতে পারবেন।" @@ -3244,4 +3248,3 @@ msgstr "জুম আউট" #: src/utils/locale.ts msgid "Zulu" msgstr "জুলু" - diff --git a/locales/ca-ES.po b/locales/ca-ES.po index 3f87e78fc..c720f27e6 100644 --- a/locales/ca-ES.po +++ b/locales/ca-ES.po @@ -3186,6 +3186,10 @@ msgstr "Les dades es desen localment" msgid "Your data is stored securely and is never shared with third parties. You can also self-host Reactive Resume on your own servers for complete control over your data." msgstr "Les dades s’emmagatzemen de manera segura i mai no es comparteixen amb tercers. També pots autoallotjar Reactive Resume als teus propis servidors per tenir un control total sobre les dades." +#: src/components/resume/store/resume.ts +msgid "Your latest changes could not be saved. Please make sure you are connected to the internet and try again." +msgstr "" + #: src/routes/auth/reset-password.tsx msgid "Your password has been reset successfully. You can now sign in with your new password." msgstr "La contrasenya s’ha restablert correctament. Ara ja pots iniciar sessió amb la contrasenya nova." @@ -3244,4 +3248,3 @@ msgstr "Allunya" #: src/utils/locale.ts msgid "Zulu" msgstr "Zulú" - diff --git a/locales/cs-CZ.po b/locales/cs-CZ.po index 81dc95caa..74d8b9305 100644 --- a/locales/cs-CZ.po +++ b/locales/cs-CZ.po @@ -3186,6 +3186,10 @@ msgstr "Vaše data jsou ukládána lokálně" msgid "Your data is stored securely and is never shared with third parties. You can also self-host Reactive Resume on your own servers for complete control over your data." msgstr "Vaše data jsou ukládána bezpečně a nikdy nejsou sdílena s třetími stranami. Reactive Resume si můžete také hostovat sami na vlastních serverech a mít tak nad svými daty plnou kontrolu." +#: src/components/resume/store/resume.ts +msgid "Your latest changes could not be saved. Please make sure you are connected to the internet and try again." +msgstr "" + #: src/routes/auth/reset-password.tsx msgid "Your password has been reset successfully. You can now sign in with your new password." msgstr "Vaše heslo bylo úspěšně obnoveno. Nyní se můžete přihlásit pomocí nového hesla." @@ -3244,4 +3248,3 @@ msgstr "Oddálit" #: src/utils/locale.ts msgid "Zulu" msgstr "Zulu" - diff --git a/locales/da-DK.po b/locales/da-DK.po index 4a63f350e..78b692188 100644 --- a/locales/da-DK.po +++ b/locales/da-DK.po @@ -3186,6 +3186,10 @@ msgstr "Dine data gemmes lokalt" msgid "Your data is stored securely and is never shared with third parties. You can also self-host Reactive Resume on your own servers for complete control over your data." msgstr "Dine data lagres sikkert og deles aldrig med tredjeparter. Du kan også selv-hoste Reactive Resume på dine egne servere for fuld kontrol over dine data." +#: src/components/resume/store/resume.ts +msgid "Your latest changes could not be saved. Please make sure you are connected to the internet and try again." +msgstr "" + #: src/routes/auth/reset-password.tsx msgid "Your password has been reset successfully. You can now sign in with your new password." msgstr "Din adgangskode er blevet nulstillet. Du kan nu logge ind med din nye adgangskode." @@ -3244,4 +3248,3 @@ msgstr "Zoom ud" #: src/utils/locale.ts msgid "Zulu" msgstr "Zulu" - diff --git a/locales/de-DE.po b/locales/de-DE.po index 2fb937d56..bb71aad57 100644 --- a/locales/de-DE.po +++ b/locales/de-DE.po @@ -3186,6 +3186,10 @@ msgstr "Deine Daten werden lokal gespeichert" msgid "Your data is stored securely and is never shared with third parties. You can also self-host Reactive Resume on your own servers for complete control over your data." msgstr "Deine Daten werden sicher gespeichert und niemals an Dritte weitergegeben. Du kannst Reactive Resume außerdem auf deinen eigenen Servern selbst hosten, um die vollständige Kontrolle über deine Daten zu behalten." +#: src/components/resume/store/resume.ts +msgid "Your latest changes could not be saved. Please make sure you are connected to the internet and try again." +msgstr "" + #: src/routes/auth/reset-password.tsx msgid "Your password has been reset successfully. You can now sign in with your new password." msgstr "Ihr Passwort wurde erfolgreich zurückgesetzt. Sie können sich nun mit Ihrem neuen Passwort anmelden." @@ -3244,4 +3248,3 @@ msgstr "Herauszoomen" #: src/utils/locale.ts msgid "Zulu" msgstr "Zulu" - diff --git a/locales/el-GR.po b/locales/el-GR.po index 573b81f3d..857406539 100644 --- a/locales/el-GR.po +++ b/locales/el-GR.po @@ -3186,6 +3186,10 @@ msgstr "Τα δεδομένα σας αποθηκεύονται τοπικά" msgid "Your data is stored securely and is never shared with third parties. You can also self-host Reactive Resume on your own servers for complete control over your data." msgstr "Τα δεδομένα σας αποθηκεύονται με ασφάλεια και δεν κοινοποιούνται ποτέ σε τρίτους. Μπορείτε επίσης να φιλοξενήσετε μόνοι σας το Reactive Resume στους δικούς σας διακομιστές για πλήρη έλεγχο των δεδομένων σας." +#: src/components/resume/store/resume.ts +msgid "Your latest changes could not be saved. Please make sure you are connected to the internet and try again." +msgstr "" + #: src/routes/auth/reset-password.tsx msgid "Your password has been reset successfully. You can now sign in with your new password." msgstr "Ο κωδικός πρόσβασής σας επαναφέρθηκε με επιτυχία. Τώρα μπορείτε να συνδεθείτε με τον νέο σας κωδικό πρόσβασης." @@ -3244,4 +3248,3 @@ msgstr "Σμίκρυνση" #: src/utils/locale.ts msgid "Zulu" msgstr "Ζουλού" - diff --git a/locales/en-GB.po b/locales/en-GB.po index 7ed9ce411..01732abac 100644 --- a/locales/en-GB.po +++ b/locales/en-GB.po @@ -3186,6 +3186,10 @@ msgstr "Your data is stored locally" msgid "Your data is stored securely and is never shared with third parties. You can also self-host Reactive Resume on your own servers for complete control over your data." msgstr "Your data is stored securely and is never shared with third parties. You can also self-host Reactive Resume on your own servers for complete control over your data." +#: src/components/resume/store/resume.ts +msgid "Your latest changes could not be saved. Please make sure you are connected to the internet and try again." +msgstr "" + #: src/routes/auth/reset-password.tsx msgid "Your password has been reset successfully. You can now sign in with your new password." msgstr "Your password has been reset successfully. You can now sign in with your new password." @@ -3244,4 +3248,3 @@ msgstr "Zoom out" #: src/utils/locale.ts msgid "Zulu" msgstr "Zulu" - diff --git a/locales/en-US.po b/locales/en-US.po index 395cc4309..d01037cc6 100644 --- a/locales/en-US.po +++ b/locales/en-US.po @@ -3181,6 +3181,10 @@ msgstr "Your data is stored locally" msgid "Your data is stored securely and is never shared with third parties. You can also self-host Reactive Resume on your own servers for complete control over your data." msgstr "Your data is stored securely and is never shared with third parties. You can also self-host Reactive Resume on your own servers for complete control over your data." +#: src/components/resume/store/resume.ts +msgid "Your latest changes could not be saved. Please make sure you are connected to the internet and try again." +msgstr "Your latest changes could not be saved. Please make sure you are connected to the internet and try again." + #: src/routes/auth/reset-password.tsx msgid "Your password has been reset successfully. You can now sign in with your new password." msgstr "Your password has been reset successfully. You can now sign in with your new password." diff --git a/locales/es-ES.po b/locales/es-ES.po index 4c91569d1..c636497d8 100644 --- a/locales/es-ES.po +++ b/locales/es-ES.po @@ -3186,6 +3186,10 @@ msgstr "Tus datos se almacenan localmente" msgid "Your data is stored securely and is never shared with third parties. You can also self-host Reactive Resume on your own servers for complete control over your data." msgstr "Tus datos se almacenan de forma segura y nunca se comparten con terceros. También puedes auto alojar Reactive Resume en tus propios servidores para tener un control total sobre tus datos." +#: src/components/resume/store/resume.ts +msgid "Your latest changes could not be saved. Please make sure you are connected to the internet and try again." +msgstr "" + #: src/routes/auth/reset-password.tsx msgid "Your password has been reset successfully. You can now sign in with your new password." msgstr "Tu contraseña se ha restablecido correctamente. Ahora puedes iniciar sesión con tu nueva contraseña." @@ -3244,4 +3248,3 @@ msgstr "Alejar" #: src/utils/locale.ts msgid "Zulu" msgstr "Zulú" - diff --git a/locales/fa-IR.po b/locales/fa-IR.po index 399a8a82a..bd0e2d262 100644 --- a/locales/fa-IR.po +++ b/locales/fa-IR.po @@ -3186,6 +3186,10 @@ msgstr "داده‌های شما به‌صورت محلی ذخیره می‌شو msgid "Your data is stored securely and is never shared with third parties. You can also self-host Reactive Resume on your own servers for complete control over your data." msgstr "داده‌های شما به‌طور ایمن ذخیره شده و هرگز با اشخاص ثالث به اشتراک گذاشته نمی‌شوند. همچنین می‌توانید Reactive Resume را روی سرورهای خود میزبانی کنید تا کنترل کامل بر داده‌های خود داشته باشید." +#: src/components/resume/store/resume.ts +msgid "Your latest changes could not be saved. Please make sure you are connected to the internet and try again." +msgstr "" + #: src/routes/auth/reset-password.tsx msgid "Your password has been reset successfully. You can now sign in with your new password." msgstr "گذرواژهٔ شما با موفقیت تنظیم مجدد شد. اکنون می‌توانید با گذرواژهٔ جدید خود وارد شوید." @@ -3244,4 +3248,3 @@ msgstr "کوچک‌نمایی" #: src/utils/locale.ts msgid "Zulu" msgstr "زولو" - diff --git a/locales/fi-FI.po b/locales/fi-FI.po index 3151e2f9e..b6b7aeaaf 100644 --- a/locales/fi-FI.po +++ b/locales/fi-FI.po @@ -3186,6 +3186,10 @@ msgstr "Tietosi tallennetaan paikallisesti" msgid "Your data is stored securely and is never shared with third parties. You can also self-host Reactive Resume on your own servers for complete control over your data." msgstr "Tietosi tallennetaan turvallisesti eikä niitä koskaan jaeta kolmansille osapuolille. Voit myös itseisännöidä Reactive Resumen omilla palvelimillasi täydellisen tietokontrollin takaamiseksi." +#: src/components/resume/store/resume.ts +msgid "Your latest changes could not be saved. Please make sure you are connected to the internet and try again." +msgstr "" + #: src/routes/auth/reset-password.tsx msgid "Your password has been reset successfully. You can now sign in with your new password." msgstr "Salasanasi on palautettu onnistuneesti. Voit nyt kirjautua sisään uudella salasanallasi." @@ -3244,4 +3248,3 @@ msgstr "Loitonna" #: src/utils/locale.ts msgid "Zulu" msgstr "Zulu" - diff --git a/locales/fr-FR.po b/locales/fr-FR.po index 535ed57ec..d48478bdf 100644 --- a/locales/fr-FR.po +++ b/locales/fr-FR.po @@ -3186,6 +3186,10 @@ msgstr "Vos données sont stockées localement" msgid "Your data is stored securely and is never shared with third parties. You can also self-host Reactive Resume on your own servers for complete control over your data." msgstr "Vos données sont stockées en toute sécurité et ne sont jamais partagées avec des tiers. Vous pouvez également héberger Reactive Resume sur vos propres serveurs pour un contrôle total de vos données." +#: src/components/resume/store/resume.ts +msgid "Your latest changes could not be saved. Please make sure you are connected to the internet and try again." +msgstr "" + #: src/routes/auth/reset-password.tsx msgid "Your password has been reset successfully. You can now sign in with your new password." msgstr "Votre mot de passe a été réinitialisé avec succès. Vous pouvez maintenant vous connecter avec votre nouveau mot de passe." @@ -3244,4 +3248,3 @@ msgstr "Zoom arrière" #: src/utils/locale.ts msgid "Zulu" msgstr "Zoulou" - diff --git a/locales/he-IL.po b/locales/he-IL.po index 745a545bf..e2cdf32b2 100644 --- a/locales/he-IL.po +++ b/locales/he-IL.po @@ -3186,6 +3186,10 @@ msgstr "הנתונים שלך מאוחסנים באופן מקומי" msgid "Your data is stored securely and is never shared with third parties. You can also self-host Reactive Resume on your own servers for complete control over your data." msgstr "הנתונים שלך מאוחסנים בצורה מאובטחת ולעולם אינם משותפים עם צדדים שלישיים. אפשר גם לארח את Reactive Resume בשרתים שלך כדי לשלוט לחלוטין בנתונים שלך." +#: src/components/resume/store/resume.ts +msgid "Your latest changes could not be saved. Please make sure you are connected to the internet and try again." +msgstr "" + #: src/routes/auth/reset-password.tsx msgid "Your password has been reset successfully. You can now sign in with your new password." msgstr "הסיסמה שלך אופסה בהצלחה. כעת אפשר להתחבר עם הסיסמה החדשה שלך." @@ -3244,4 +3248,3 @@ msgstr "התרחקות" #: src/utils/locale.ts msgid "Zulu" msgstr "זולו" - diff --git a/locales/hi-IN.po b/locales/hi-IN.po index 2fa22bc5d..8b5ad4884 100644 --- a/locales/hi-IN.po +++ b/locales/hi-IN.po @@ -3186,6 +3186,10 @@ msgstr "आपका डेटा लोकल रूप से संग्र msgid "Your data is stored securely and is never shared with third parties. You can also self-host Reactive Resume on your own servers for complete control over your data." msgstr "आपका डेटा सुरक्षित रूप से संग्रहीत है और कभी भी थर्ड पार्टियों के साथ साझा नहीं किया जाता। आप अपने सर्वर्स पर Reactive Resume को स्व‑होस्ट भी कर सकते हैं ताकि अपने डेटा पर पूर्ण नियंत्रण रख सकें।" +#: src/components/resume/store/resume.ts +msgid "Your latest changes could not be saved. Please make sure you are connected to the internet and try again." +msgstr "" + #: src/routes/auth/reset-password.tsx msgid "Your password has been reset successfully. You can now sign in with your new password." msgstr "आपका पासवर्ड सफलतापूर्वक रीसेट कर दिया गया है। अब आप अपने नए पासवर्ड से साइन इन कर सकते हैं।" @@ -3244,4 +3248,3 @@ msgstr "ज़ूम आउट" #: src/utils/locale.ts msgid "Zulu" msgstr "ज़ुलु" - diff --git a/locales/hu-HU.po b/locales/hu-HU.po index 0055baaa3..c7b81ae94 100644 --- a/locales/hu-HU.po +++ b/locales/hu-HU.po @@ -3186,6 +3186,10 @@ msgstr "Az adataid helyben tárolódnak" msgid "Your data is stored securely and is never shared with third parties. You can also self-host Reactive Resume on your own servers for complete control over your data." msgstr "Az adataid biztonságosan tároljuk, és soha nem osztjuk meg harmadik felekkel. A Reactive Resume‑t saját szervereiden is hosztolhatod, hogy teljes kontrollod legyen az adataid felett." +#: src/components/resume/store/resume.ts +msgid "Your latest changes could not be saved. Please make sure you are connected to the internet and try again." +msgstr "" + #: src/routes/auth/reset-password.tsx msgid "Your password has been reset successfully. You can now sign in with your new password." msgstr "A jelszavadat sikeresen visszaállítottuk. Mostantól az új jelszóval jelentkezhetsz be." @@ -3244,4 +3248,3 @@ msgstr "Kicsinyítés" #: src/utils/locale.ts msgid "Zulu" msgstr "Zulu" - diff --git a/locales/id-ID.po b/locales/id-ID.po index c0cecb100..8bb43ed42 100644 --- a/locales/id-ID.po +++ b/locales/id-ID.po @@ -3186,6 +3186,10 @@ msgstr "Data Anda disimpan secara lokal" msgid "Your data is stored securely and is never shared with third parties. You can also self-host Reactive Resume on your own servers for complete control over your data." msgstr "Data Anda disimpan dengan aman dan tidak pernah dibagikan dengan pihak ketiga. Anda juga dapat self-host Reactive Resume di server Anda sendiri untuk kontrol penuh atas data Anda." +#: src/components/resume/store/resume.ts +msgid "Your latest changes could not be saved. Please make sure you are connected to the internet and try again." +msgstr "" + #: src/routes/auth/reset-password.tsx msgid "Your password has been reset successfully. You can now sign in with your new password." msgstr "Kata sandi Anda telah berhasil diatur ulang. Anda sekarang dapat masuk dengan kata sandi baru Anda." @@ -3244,4 +3248,3 @@ msgstr "Perkecil" #: src/utils/locale.ts msgid "Zulu" msgstr "Zulu" - diff --git a/locales/it-IT.po b/locales/it-IT.po index d6300bacd..c6ed30965 100644 --- a/locales/it-IT.po +++ b/locales/it-IT.po @@ -3186,6 +3186,10 @@ msgstr "I tuoi dati vengono memorizzati localmente" msgid "Your data is stored securely and is never shared with third parties. You can also self-host Reactive Resume on your own servers for complete control over your data." msgstr "I tuoi dati sono archiviati in modo sicuro e non vengono mai condivisi con terze parti. Puoi anche fare self-host di Reactive Resume sui tuoi server per avere il pieno controllo dei tuoi dati." +#: src/components/resume/store/resume.ts +msgid "Your latest changes could not be saved. Please make sure you are connected to the internet and try again." +msgstr "" + #: src/routes/auth/reset-password.tsx msgid "Your password has been reset successfully. You can now sign in with your new password." msgstr "La tua password è stata reimpostata correttamente. Ora puoi accedere con la tua nuova password." @@ -3244,4 +3248,3 @@ msgstr "Rimpicciolisci" #: src/utils/locale.ts msgid "Zulu" msgstr "Zulu" - diff --git a/locales/ja-JP.po b/locales/ja-JP.po index c52e4680a..5b2f76b0a 100644 --- a/locales/ja-JP.po +++ b/locales/ja-JP.po @@ -3186,6 +3186,10 @@ msgstr "あなたのデータはローカルに保存されます。" msgid "Your data is stored securely and is never shared with third parties. You can also self-host Reactive Resume on your own servers for complete control over your data." msgstr "あなたのデータは安全に保存され、第三者と共有されることはありません。また、完全にデータを管理したい場合は、Reactive Resume を自分のサーバーにセルフホストすることもできます。" +#: src/components/resume/store/resume.ts +msgid "Your latest changes could not be saved. Please make sure you are connected to the internet and try again." +msgstr "" + #: src/routes/auth/reset-password.tsx msgid "Your password has been reset successfully. You can now sign in with your new password." msgstr "パスワードを正常にリセットしました。新しいパスワードでサインインできます。" @@ -3244,4 +3248,3 @@ msgstr "ズームアウト" #: src/utils/locale.ts msgid "Zulu" msgstr "ズールー語" - diff --git a/locales/km-KH.po b/locales/km-KH.po index c15475b34..e8575e5b5 100644 --- a/locales/km-KH.po +++ b/locales/km-KH.po @@ -3186,6 +3186,10 @@ msgstr "ទិន្នន័យ​របស់​អ្នក​ត្រូវ msgid "Your data is stored securely and is never shared with third parties. You can also self-host Reactive Resume on your own servers for complete control over your data." msgstr "ទិន្នន័យ​របស់​អ្នក​ត្រូវ​បាន​រក្សាទុក​ដោយ​សុវត្ថិភាព ហើយ​មិនដែល​ត្រូវ​បាន​ចែករំលែក​ជាមួយ​ភាគី​ទីបី​ទេ។ អ្នក​ក៏​អាច​ដំឡើង Reactive Resume លើ​ម៉ាស៊ីន​បម្រើ​ផ្ទាល់ខ្លួន​របស់​អ្នក ដើម្បី​មាន​សិទ្ធિ​ត្រួតត្រា​លើ​ទិន្នន័យ​របស់​អ្នក​ដល់​ខ្ពស់​បំផុត​ផង​ដែរ។" +#: src/components/resume/store/resume.ts +msgid "Your latest changes could not be saved. Please make sure you are connected to the internet and try again." +msgstr "" + #: src/routes/auth/reset-password.tsx msgid "Your password has been reset successfully. You can now sign in with your new password." msgstr "ពាក្យសម្ងាត់​របស់​អ្នក​ត្រូវ​បាន​កំណត់​ឡើងវិញ​ដោយ​ជោគជ័យ។ ឥឡូវ​អ្នក​អាច​ចូល​ដោយ​ប្រើ​ពាក្យសម្ងាត់​ថ្មី​របស់​អ្នកបាន​ហើយ។" @@ -3244,4 +3248,3 @@ msgstr "បង្រួម" #: src/utils/locale.ts msgid "Zulu" msgstr "Zulu" - diff --git a/locales/kn-IN.po b/locales/kn-IN.po index 855f0e299..b309d07ee 100644 --- a/locales/kn-IN.po +++ b/locales/kn-IN.po @@ -3186,6 +3186,10 @@ msgstr "ನಿಮ್ಮ ಡೇಟಾವನ್ನು ಸ್ಥಳೀಯವಾಗ msgid "Your data is stored securely and is never shared with third parties. You can also self-host Reactive Resume on your own servers for complete control over your data." msgstr "ನಿಮ್ಮ ಡೇಟಾವನ್ನು ಸುರಕ್ಷಿತವಾಗಿ ಸಂಗ್ರಹಿಸಲಾಗುತ್ತದೆ ಮತ್ತು ಎಂದಿಗೂ ತೃತೀಯಪಕ್ಷಗಳೊಂದಿಗೆ ಹಂಚಿಕೊಳ್ಳಲಾಗುವುದಿಲ್ಲ. ನಿಮ್ಮ ಡೇಟಾದ ಮೇಲೆ ಸಂಪೂರ್ಣ ನಿಯಂತ್ರಣಕ್ಕಾಗಿ ನಿಮ್ಮದೇ ಸರ್ವರ್‌ಗಳಲ್ಲಿ Reactive Resume ಅನ್ನು ಸ್ವಯಂ-ಹೋಸ್ಟ್ ಮಾಡಬಹುದು." +#: src/components/resume/store/resume.ts +msgid "Your latest changes could not be saved. Please make sure you are connected to the internet and try again." +msgstr "" + #: src/routes/auth/reset-password.tsx msgid "Your password has been reset successfully. You can now sign in with your new password." msgstr "ನಿಮ್ಮ ಪಾಸ್‌ವರ್ಡ್ ಅನ್ನು ಯಶಸ್ವಿಯಾಗಿ ಮರುಹೊಂದಿಸಲಾಗಿದೆ. ಈಗ ನೀವು ನಿಮ್ಮ ಹೊಸ ಪಾಸ್‌ವರ್ಡ್ ಬಳಸಿಕೊಂಡು ಸೈನ್ ಇನ್ ಮಾಡಬಹುದು." @@ -3244,4 +3248,3 @@ msgstr "ಗಾತ್ರ ಕುಗ್ಗಿಸಿ" #: src/utils/locale.ts msgid "Zulu" msgstr "ಜೂಲೂ" - diff --git a/locales/ko-KR.po b/locales/ko-KR.po index 2b35b2c98..6a17201ff 100644 --- a/locales/ko-KR.po +++ b/locales/ko-KR.po @@ -3186,6 +3186,10 @@ msgstr "데이터는 브라우저에 로컬로 저장됩니다" msgid "Your data is stored securely and is never shared with third parties. You can also self-host Reactive Resume on your own servers for complete control over your data." msgstr "데이터는 안전하게 저장되며 제3자와 공유되지 않습니다. 또한 Reactive Resume를 직접 서버에 호스팅해 데이터에 대한 완전한 제어권을 가질 수 있습니다." +#: src/components/resume/store/resume.ts +msgid "Your latest changes could not be saved. Please make sure you are connected to the internet and try again." +msgstr "" + #: src/routes/auth/reset-password.tsx msgid "Your password has been reset successfully. You can now sign in with your new password." msgstr "비밀번호가 성공적으로 재설정되었습니다. 이제 새 비밀번호로 로그인할 수 있습니다." @@ -3244,4 +3248,3 @@ msgstr "축소" #: src/utils/locale.ts msgid "Zulu" msgstr "줄루어" - diff --git a/locales/lt-LT.po b/locales/lt-LT.po index 6e53bcba3..5a5b9ec55 100644 --- a/locales/lt-LT.po +++ b/locales/lt-LT.po @@ -3186,6 +3186,10 @@ msgstr "Jūsų duomenys saugomi vietoje" msgid "Your data is stored securely and is never shared with third parties. You can also self-host Reactive Resume on your own servers for complete control over your data." msgstr "Jūsų duomenys saugiai laikomi ir niekada nebendrinami su trečiosiomis šalimis. Taip pat galite savarankiškai talpinti „Reactive Resume“ savo serveriuose ir visiškai kontroliuoti savo duomenis." +#: src/components/resume/store/resume.ts +msgid "Your latest changes could not be saved. Please make sure you are connected to the internet and try again." +msgstr "" + #: src/routes/auth/reset-password.tsx msgid "Your password has been reset successfully. You can now sign in with your new password." msgstr "Jūsų slaptažodis sėkmingai atstatytas. Dabar galite prisijungti naudodami naują slaptažodį." @@ -3244,4 +3248,3 @@ msgstr "Tolinti" #: src/utils/locale.ts msgid "Zulu" msgstr "Zulų" - diff --git a/locales/lv-LV.po b/locales/lv-LV.po index 1720a71ff..9e26181f8 100644 --- a/locales/lv-LV.po +++ b/locales/lv-LV.po @@ -3186,6 +3186,10 @@ msgstr "Jūsu dati tiek glabāti lokāli" msgid "Your data is stored securely and is never shared with third parties. You can also self-host Reactive Resume on your own servers for complete control over your data." msgstr "Jūsu dati tiek glabāti droši un nekad netiek kopīgoti ar trešajām pusēm. Reactive Resume varat arī pašhostēt uz saviem serveriem, lai pilnībā kontrolētu savus datus." +#: src/components/resume/store/resume.ts +msgid "Your latest changes could not be saved. Please make sure you are connected to the internet and try again." +msgstr "" + #: src/routes/auth/reset-password.tsx msgid "Your password has been reset successfully. You can now sign in with your new password." msgstr "Jūsu parole ir veiksmīgi atiestatīta. Tagad varat pieteikties ar jauno paroli." @@ -3244,4 +3248,3 @@ msgstr "Tālināt" #: src/utils/locale.ts msgid "Zulu" msgstr "Zulu" - diff --git a/locales/ml-IN.po b/locales/ml-IN.po index cb45eca5a..0eb8540fb 100644 --- a/locales/ml-IN.po +++ b/locales/ml-IN.po @@ -3186,6 +3186,10 @@ msgstr "നിങ്ങളുടെ ഡാറ്റ നിങ്ങളുടെ msgid "Your data is stored securely and is never shared with third parties. You can also self-host Reactive Resume on your own servers for complete control over your data." msgstr "നിങ്ങളുടെ ഡാറ്റ സുരക്ഷിതമായി സൂക്ഷിക്കപ്പെടുന്നു, ഒരിക്കലും മൂന്നാം കക്ഷികളുമായി പങ്കിടുന്നില്ല. നിങ്ങളുടെ ഡാറ്റയ്‌ക്ക് പൂർണ്ണ നിയന്ത്രണം നേടാൻ Reactive Resume നിങ്ങളുടെ സ്വന്തം സെർവറുകളിൽ സെൽഫ്‑ഹോസ്റ്റ് ചെയ്യാനും നിങ്ങൾക്ക് കഴിയും." +#: src/components/resume/store/resume.ts +msgid "Your latest changes could not be saved. Please make sure you are connected to the internet and try again." +msgstr "" + #: src/routes/auth/reset-password.tsx msgid "Your password has been reset successfully. You can now sign in with your new password." msgstr "നിങ്ങളുടെ പാസ്‌വേഡ് വിജയകരമായി റീസെറ്റ് ചെയ്തിട്ടുണ്ട്. ഇനി നിങ്ങൾക്ക് പുതിയ പാസ്‌വേഡ് ഉപയോഗിച്ച് സൈൻ ഇൻ ചെയ്യാം." @@ -3244,4 +3248,3 @@ msgstr "സൂം ഔട്ട് ചെയ്യുക" #: src/utils/locale.ts msgid "Zulu" msgstr "സൂളു" - diff --git a/locales/mr-IN.po b/locales/mr-IN.po index 24de31db1..683058b18 100644 --- a/locales/mr-IN.po +++ b/locales/mr-IN.po @@ -3186,6 +3186,10 @@ msgstr "तुमचा डेटा लोकली साठवला जा msgid "Your data is stored securely and is never shared with third parties. You can also self-host Reactive Resume on your own servers for complete control over your data." msgstr "तुमचा डेटा सुरक्षितपणे साठवला जातो आणि तृतीय पक्षांसोबत कधीही शेअर केला जात नाही. तुम्ही पूर्ण डेटा कंट्रोलसाठी Reactive Resume स्वतःच्या सर्व्हरवरही होस्ट करू शकता." +#: src/components/resume/store/resume.ts +msgid "Your latest changes could not be saved. Please make sure you are connected to the internet and try again." +msgstr "" + #: src/routes/auth/reset-password.tsx msgid "Your password has been reset successfully. You can now sign in with your new password." msgstr "तुमचा पासवर्ड यशस्वीरित्या रिसेट केला गेला आहे. तुम्ही आता नवीन पासवर्डने साइन इन करू शकता." @@ -3244,4 +3248,3 @@ msgstr "बाहेर झूम करा" #: src/utils/locale.ts msgid "Zulu" msgstr "झुलू" - diff --git a/locales/ms-MY.po b/locales/ms-MY.po index c80f158cc..e44b7e71e 100644 --- a/locales/ms-MY.po +++ b/locales/ms-MY.po @@ -3186,6 +3186,10 @@ msgstr "Data anda disimpan secara setempat" msgid "Your data is stored securely and is never shared with third parties. You can also self-host Reactive Resume on your own servers for complete control over your data." msgstr "Data anda disimpan dengan selamat dan tidak pernah dikongsi dengan pihak ketiga. Anda juga boleh menghoskan sendiri Reactive Resume pada pelayan anda untuk kawalan penuh ke atas data anda." +#: src/components/resume/store/resume.ts +msgid "Your latest changes could not be saved. Please make sure you are connected to the internet and try again." +msgstr "" + #: src/routes/auth/reset-password.tsx msgid "Your password has been reset successfully. You can now sign in with your new password." msgstr "Kata laluan anda telah berjaya ditetapkan semula. Anda kini boleh log masuk dengan kata laluan baharu anda." @@ -3244,4 +3248,3 @@ msgstr "Zum keluar" #: src/utils/locale.ts msgid "Zulu" msgstr "Zulu" - diff --git a/locales/ne-NP.po b/locales/ne-NP.po index bb54efc58..d0773d221 100644 --- a/locales/ne-NP.po +++ b/locales/ne-NP.po @@ -3186,6 +3186,10 @@ msgstr "तपाईंको डाटा स्थानीय रूपमा msgid "Your data is stored securely and is never shared with third parties. You can also self-host Reactive Resume on your own servers for complete control over your data." msgstr "तपाईंको डाटा सुरक्षित रूपमा भण्डारण गरिएको छ र तेस्रो पक्षसँग कहिल्यै सेयर गरिँदैन। तपाईंले चाहनु भएमा आफ्नै सर्भरमा Reactive Resume लाई self-host गरेर आफ्नो डाटामा पूर्ण नियन्त्रण राख्न सक्नुहुन्छ।" +#: src/components/resume/store/resume.ts +msgid "Your latest changes could not be saved. Please make sure you are connected to the internet and try again." +msgstr "" + #: src/routes/auth/reset-password.tsx msgid "Your password has been reset successfully. You can now sign in with your new password." msgstr "तपाईंको पासवर्ड सफलतापूर्वक रिसेट भयो। अब नयाँ पासवर्ड प्रयोग गरेर साइन इन गर्न सक्नुहुन्छ।" @@ -3244,4 +3248,3 @@ msgstr "जूम आउट" #: src/utils/locale.ts msgid "Zulu" msgstr "जुलु" - diff --git a/locales/nl-NL.po b/locales/nl-NL.po index 1ad3453f0..882f81dae 100644 --- a/locales/nl-NL.po +++ b/locales/nl-NL.po @@ -3186,6 +3186,10 @@ msgstr "Uw gegevens worden lokaal opgeslagen" msgid "Your data is stored securely and is never shared with third parties. You can also self-host Reactive Resume on your own servers for complete control over your data." msgstr "Uw gegevens worden veilig opgeslagen en nooit met derden gedeeld. U kunt Reactive Resume ook zelf op uw eigen servers hosten voor volledige controle over uw gegevens." +#: src/components/resume/store/resume.ts +msgid "Your latest changes could not be saved. Please make sure you are connected to the internet and try again." +msgstr "" + #: src/routes/auth/reset-password.tsx msgid "Your password has been reset successfully. You can now sign in with your new password." msgstr "Uw wachtwoord is succesvol gereset. U kunt nu met uw nieuwe wachtwoord inloggen." @@ -3244,4 +3248,3 @@ msgstr "Uitzoomen" #: src/utils/locale.ts msgid "Zulu" msgstr "Zulu" - diff --git a/locales/no-NO.po b/locales/no-NO.po index 4a316da3a..314c7540a 100644 --- a/locales/no-NO.po +++ b/locales/no-NO.po @@ -3186,6 +3186,10 @@ msgstr "Dataene dine lagres lokalt" msgid "Your data is stored securely and is never shared with third parties. You can also self-host Reactive Resume on your own servers for complete control over your data." msgstr "Dataene dine lagres sikkert og deles aldri med tredjeparter. Du kan også selv-hoste Reactive Resume på egne servere for full kontroll over dataene dine." +#: src/components/resume/store/resume.ts +msgid "Your latest changes could not be saved. Please make sure you are connected to the internet and try again." +msgstr "" + #: src/routes/auth/reset-password.tsx msgid "Your password has been reset successfully. You can now sign in with your new password." msgstr "Passordet ditt er tilbakestilt. Du kan nå logge inn med det nye passordet ditt." @@ -3244,4 +3248,3 @@ msgstr "Zoom ut" #: src/utils/locale.ts msgid "Zulu" msgstr "Zulu" - diff --git a/locales/or-IN.po b/locales/or-IN.po index b540319e2..dea2101cb 100644 --- a/locales/or-IN.po +++ b/locales/or-IN.po @@ -3186,6 +3186,10 @@ msgstr "ଆପଣଙ୍କ ତଥ୍ୟ ସ୍ଥାନୀୟ ଭାବରେ msgid "Your data is stored securely and is never shared with third parties. You can also self-host Reactive Resume on your own servers for complete control over your data." msgstr "ଆପଣଙ୍କ ତଥ୍ୟ ସୁରକ୍ଷିତ ଭାବରେ ସଞ୍ଚୟ ହୋଇଛି ଏବଂ କେବେ ମଧ୍ୟ ତୃତୀୟ ପକ୍ଷ ସହ ସେୟାର ହୋଇନାହିଁ। ଆପଣ ଆପଣଙ୍କ ତଥ୍ୟ ଉପରେ ପୂର୍ଣ୍ଣ ନିୟନ୍ତ୍ରଣ ପାଇଁ ଆପଣଙ୍କ ନିଜ ସର୍ଭରଗୁଡିକରେ Reactive Resume କୁ ସ୍ୱୟଂ‑ହୋଷ୍ଟ କରିପାରିବେ।" +#: src/components/resume/store/resume.ts +msgid "Your latest changes could not be saved. Please make sure you are connected to the internet and try again." +msgstr "" + #: src/routes/auth/reset-password.tsx msgid "Your password has been reset successfully. You can now sign in with your new password." msgstr "ଆପଣଙ୍କ ପାସୱାର୍ଡ ସଫଳତାର ସହ ପୁନଃ ସେଟ୍ କରାଯାଇଛି। ଆପଣ ଏବେ ନୂତନ ପାସୱାର୍ଡ ସହ ସାଇନ୍ ଇନ୍ କରିପାରିବେ।" @@ -3244,4 +3248,3 @@ msgstr "ଜୁମ୍ ଆଉଟ୍ କରନ୍ତୁ" #: src/utils/locale.ts msgid "Zulu" msgstr "ଜୁଲୁ" - diff --git a/locales/pl-PL.po b/locales/pl-PL.po index 3ebd1df03..8a6d5eac0 100644 --- a/locales/pl-PL.po +++ b/locales/pl-PL.po @@ -3186,6 +3186,10 @@ msgstr "Twoje dane są przechowywane lokalnie" msgid "Your data is stored securely and is never shared with third parties. You can also self-host Reactive Resume on your own servers for complete control over your data." msgstr "Twoje dane są przechowywane w bezpieczny sposób i nigdy nie są udostępniane podmiotom trzecim. Możesz też samodzielnie hostować Reactive Resume na własnych serwerach, aby mieć pełną kontrolę nad swoimi danymi." +#: src/components/resume/store/resume.ts +msgid "Your latest changes could not be saved. Please make sure you are connected to the internet and try again." +msgstr "" + #: src/routes/auth/reset-password.tsx msgid "Your password has been reset successfully. You can now sign in with your new password." msgstr "Twoje hasło zostało pomyślnie zresetowane. Możesz teraz zalogować się przy użyciu nowego hasła." @@ -3244,4 +3248,3 @@ msgstr "Pomniejsz" #: src/utils/locale.ts msgid "Zulu" msgstr "Zulu" - diff --git a/locales/pt-BR.po b/locales/pt-BR.po index 1fc557f62..574a0d8a0 100644 --- a/locales/pt-BR.po +++ b/locales/pt-BR.po @@ -3186,6 +3186,10 @@ msgstr "Seus dados são armazenados localmente" msgid "Your data is stored securely and is never shared with third parties. You can also self-host Reactive Resume on your own servers for complete control over your data." msgstr "Seus dados são armazenados com segurança e nunca são compartilhados com terceiros. Você também pode auto-hospedar o Reactive Resume em seus próprios servidores para ter controle total sobre seus dados." +#: src/components/resume/store/resume.ts +msgid "Your latest changes could not be saved. Please make sure you are connected to the internet and try again." +msgstr "" + #: src/routes/auth/reset-password.tsx msgid "Your password has been reset successfully. You can now sign in with your new password." msgstr "Sua senha foi redefinida com sucesso. Agora você pode entrar com sua nova senha." @@ -3244,4 +3248,3 @@ msgstr "Diminuir zoom" #: src/utils/locale.ts msgid "Zulu" msgstr "Zulu" - diff --git a/locales/pt-PT.po b/locales/pt-PT.po index b691f9185..bcb1edeeb 100644 --- a/locales/pt-PT.po +++ b/locales/pt-PT.po @@ -3186,6 +3186,10 @@ msgstr "Os seus dados são armazenados localmente" msgid "Your data is stored securely and is never shared with third parties. You can also self-host Reactive Resume on your own servers for complete control over your data." msgstr "Os seus dados são armazenados em segurança e nunca são partilhados com terceiros. Também pode auto-hospedar o Reactive Resume nos seus próprios servidores para ter controlo total sobre os seus dados." +#: src/components/resume/store/resume.ts +msgid "Your latest changes could not be saved. Please make sure you are connected to the internet and try again." +msgstr "" + #: src/routes/auth/reset-password.tsx msgid "Your password has been reset successfully. You can now sign in with your new password." msgstr "A sua senha foi reposta com sucesso. Já pode iniciar sessão com a sua nova senha." @@ -3244,4 +3248,3 @@ msgstr "Afastar" #: src/utils/locale.ts msgid "Zulu" msgstr "Zulu" - diff --git a/locales/ro-RO.po b/locales/ro-RO.po index 90fd36e8b..ced7b40fd 100644 --- a/locales/ro-RO.po +++ b/locales/ro-RO.po @@ -3186,6 +3186,10 @@ msgstr "Datele dvs. sunt stocate local" msgid "Your data is stored securely and is never shared with third parties. You can also self-host Reactive Resume on your own servers for complete control over your data." msgstr "Datele dvs. sunt stocate în siguranță și nu sunt niciodată partajate cu terți. De asemenea, puteți auto-găzdui Reactive Resume pe propriile servere pentru control complet asupra datelor." +#: src/components/resume/store/resume.ts +msgid "Your latest changes could not be saved. Please make sure you are connected to the internet and try again." +msgstr "" + #: src/routes/auth/reset-password.tsx msgid "Your password has been reset successfully. You can now sign in with your new password." msgstr "Parola a fost resetată cu succes. Vă puteți autentifica acum cu noua parolă." @@ -3244,4 +3248,3 @@ msgstr "Micșorează" #: src/utils/locale.ts msgid "Zulu" msgstr "Zulu" - diff --git a/locales/ru-RU.po b/locales/ru-RU.po index f2e1fdd49..76a64c089 100644 --- a/locales/ru-RU.po +++ b/locales/ru-RU.po @@ -3186,6 +3186,10 @@ msgstr "Ваши данные хранятся локально" msgid "Your data is stored securely and is never shared with third parties. You can also self-host Reactive Resume on your own servers for complete control over your data." msgstr "Ваши данные надежно хранятся и никогда не передаются третьим лицам. Вы также можете развернуть Reactive Resume на своих серверах, чтобы полностью контролировать свои данные." +#: src/components/resume/store/resume.ts +msgid "Your latest changes could not be saved. Please make sure you are connected to the internet and try again." +msgstr "" + #: src/routes/auth/reset-password.tsx msgid "Your password has been reset successfully. You can now sign in with your new password." msgstr "Ваш пароль был успешно сброшен. Теперь вы можете войти с новым паролем." @@ -3244,4 +3248,3 @@ msgstr "Уменьшить" #: src/utils/locale.ts msgid "Zulu" msgstr "Зулу" - diff --git a/locales/sk-SK.po b/locales/sk-SK.po index 092a49420..d4892a22c 100644 --- a/locales/sk-SK.po +++ b/locales/sk-SK.po @@ -3186,6 +3186,10 @@ msgstr "Tvoje dáta sú uložené lokálne" msgid "Your data is stored securely and is never shared with third parties. You can also self-host Reactive Resume on your own servers for complete control over your data." msgstr "Tvoje dáta sú bezpečne uložené a nikdy sa nezdieľajú s tretími stranami. Reactive Resume si môžeš tiež hostovať na vlastných serveroch a mať nad dátami úplnú kontrolu." +#: src/components/resume/store/resume.ts +msgid "Your latest changes could not be saved. Please make sure you are connected to the internet and try again." +msgstr "" + #: src/routes/auth/reset-password.tsx msgid "Your password has been reset successfully. You can now sign in with your new password." msgstr "Tvoje heslo bolo úspešne obnovené. Teraz sa môžeš prihlásiť s novým heslom." @@ -3244,4 +3248,3 @@ msgstr "Oddialiť" #: src/utils/locale.ts msgid "Zulu" msgstr "Zulu" - diff --git a/locales/sl-SI.po b/locales/sl-SI.po index a3e4b0f17..6e0bbbef7 100644 --- a/locales/sl-SI.po +++ b/locales/sl-SI.po @@ -3186,6 +3186,10 @@ msgstr "Vaši podatki so shranjeni lokalno" msgid "Your data is stored securely and is never shared with third parties. You can also self-host Reactive Resume on your own servers for complete control over your data." msgstr "Vaši podatki so varno shranjeni in se nikoli ne delijo s tretjimi osebami. Reactive Življenjepis lahko tudi gostite sami na svojih strežnikih za popoln nadzor nad svojimi podatki." +#: src/components/resume/store/resume.ts +msgid "Your latest changes could not be saved. Please make sure you are connected to the internet and try again." +msgstr "" + #: src/routes/auth/reset-password.tsx msgid "Your password has been reset successfully. You can now sign in with your new password." msgstr "Vaše geslo je bilo uspešno ponastavljeno. Zdaj se lahko prijavite z novim geslom." @@ -3244,4 +3248,3 @@ msgstr "Oddalji" #: src/utils/locale.ts msgid "Zulu" msgstr "Zuluščina" - diff --git a/locales/sq-AL.po b/locales/sq-AL.po index d537c33a8..2c75ea805 100644 --- a/locales/sq-AL.po +++ b/locales/sq-AL.po @@ -3186,6 +3186,10 @@ msgstr "Të dhënat tuaja ruhen lokalisht" msgid "Your data is stored securely and is never shared with third parties. You can also self-host Reactive Resume on your own servers for complete control over your data." msgstr "Të dhënat tuaja ruhen në mënyrë të sigurt dhe nuk ndahen kurrë me palë të treta. Ju gjithashtu mund ta self-host-oni Reactive Resume në serverët tuaj për kontroll të plotë mbi të dhënat tuaja." +#: src/components/resume/store/resume.ts +msgid "Your latest changes could not be saved. Please make sure you are connected to the internet and try again." +msgstr "" + #: src/routes/auth/reset-password.tsx msgid "Your password has been reset successfully. You can now sign in with your new password." msgstr "Fjalëkalimi juaj u rivendos me sukses. Tani mund të hyni me fjalëkalimin tuaj të ri." @@ -3244,4 +3248,3 @@ msgstr "Zvogëlo" #: src/utils/locale.ts msgid "Zulu" msgstr "Zulu" - diff --git a/locales/sr-SP.po b/locales/sr-SP.po index 77468f3e1..98ffb87d0 100644 --- a/locales/sr-SP.po +++ b/locales/sr-SP.po @@ -3186,6 +3186,10 @@ msgstr "Ваши подаци се чувају локално" msgid "Your data is stored securely and is never shared with third parties. You can also self-host Reactive Resume on your own servers for complete control over your data." msgstr "Ваши подаци се чувају безбедно и никада се не деле са трећим странама. Такође можете самостално хостовати Реактивни Резиме на сопственим серверима за потпуну контролу над подацима." +#: src/components/resume/store/resume.ts +msgid "Your latest changes could not be saved. Please make sure you are connected to the internet and try again." +msgstr "" + #: src/routes/auth/reset-password.tsx msgid "Your password has been reset successfully. You can now sign in with your new password." msgstr "Ваша лозинка је успешно ресетована. Сада можете да се пријавите новом лозинком." @@ -3244,4 +3248,3 @@ msgstr "Умањи" #: src/utils/locale.ts msgid "Zulu" msgstr "Зулу" - diff --git a/locales/sv-SE.po b/locales/sv-SE.po index e75ccfe17..c47112507 100644 --- a/locales/sv-SE.po +++ b/locales/sv-SE.po @@ -3186,6 +3186,10 @@ msgstr "Dina data lagras lokalt" msgid "Your data is stored securely and is never shared with third parties. You can also self-host Reactive Resume on your own servers for complete control over your data." msgstr "Dina data lagras säkert och delas aldrig med tredje part. Du kan också själv hosta Reactive Resume på dina egna servrar för full kontroll över dina data." +#: src/components/resume/store/resume.ts +msgid "Your latest changes could not be saved. Please make sure you are connected to the internet and try again." +msgstr "" + #: src/routes/auth/reset-password.tsx msgid "Your password has been reset successfully. You can now sign in with your new password." msgstr "Ditt lösenord har återställts. Du kan nu logga in med ditt nya lösenord." @@ -3244,4 +3248,3 @@ msgstr "Zooma ut" #: src/utils/locale.ts msgid "Zulu" msgstr "Zulu" - diff --git a/locales/ta-IN.po b/locales/ta-IN.po index 664b2082c..8dc5b82ba 100644 --- a/locales/ta-IN.po +++ b/locales/ta-IN.po @@ -3186,6 +3186,10 @@ msgstr "உங்கள் தரவு உங்கள் உலாவியி msgid "Your data is stored securely and is never shared with third parties. You can also self-host Reactive Resume on your own servers for complete control over your data." msgstr "உங்கள் தரவு பாதுகாப்பாக சேமிக்கப்படுகிறது மற்றும் மூன்றாம் தரப்புகளுடன் ஒருபோதும் பகிரப்படாது. உங்கள் தரவு மீது முழு கட்டுப்பாட்டிற்காக Reactive Resume-ஐ உங்கள் சொந்த சேவையகங்களில் தானே ஹோஸ்ட் செய்யவும் முடியும்." +#: src/components/resume/store/resume.ts +msgid "Your latest changes could not be saved. Please make sure you are connected to the internet and try again." +msgstr "" + #: src/routes/auth/reset-password.tsx msgid "Your password has been reset successfully. You can now sign in with your new password." msgstr "உங்கள் கடவுச்சொல் வெற்றிகரமாக மீட்டமைக்கப்பட்டுள்ளது. இப்போது உங்கள் புதிய கடவுச்சொல்லைப் பயன்படுத்தி உள்நுழையலாம்." @@ -3244,4 +3248,3 @@ msgstr "சிறிதாக்கு" #: src/utils/locale.ts msgid "Zulu" msgstr "ஜூலு" - diff --git a/locales/te-IN.po b/locales/te-IN.po index e37ffa10b..013ed73ee 100644 --- a/locales/te-IN.po +++ b/locales/te-IN.po @@ -3186,6 +3186,10 @@ msgstr "మీ డేటా లోకల్‌గా నిల్వ చేయ msgid "Your data is stored securely and is never shared with third parties. You can also self-host Reactive Resume on your own servers for complete control over your data." msgstr "మీ డేటా భద్రంగా నిల్వ చేయబడుతుంది, మూడవ పక్షాలతో ఎప్పుడూ పంచుకోబడదు. మీ డేటాపై పూర్తి నియంత్రణ కోసం మీరు Reactive Resumeను మీ సొంత సర్వర్లపై హోస్ట్ చేసుకోవచ్చు." +#: src/components/resume/store/resume.ts +msgid "Your latest changes could not be saved. Please make sure you are connected to the internet and try again." +msgstr "" + #: src/routes/auth/reset-password.tsx msgid "Your password has been reset successfully. You can now sign in with your new password." msgstr "మీ పాస్‌వర్డ్ విజయవంతంగా రీసెట్ చేయబడింది. ఇప్పుడు మీ కొత్త పాస్‌వర్డ్‌తో సైన్ ఇన్ చేయవచ్చు." @@ -3244,4 +3248,3 @@ msgstr "జూమ్ అవుట్ చేయండి" #: src/utils/locale.ts msgid "Zulu" msgstr "జులు" - diff --git a/locales/th-TH.po b/locales/th-TH.po index 233655e18..2ab375113 100644 --- a/locales/th-TH.po +++ b/locales/th-TH.po @@ -3186,6 +3186,10 @@ msgstr "ข้อมูลของคุณถูกจัดเก็บไว msgid "Your data is stored securely and is never shared with third parties. You can also self-host Reactive Resume on your own servers for complete control over your data." msgstr "ข้อมูลของคุณถูกจัดเก็บไว้อย่างปลอดภัยและจะไม่ถูกแชร์กับบุคคลที่สาม คุณยังสามารถโฮสต์ Reactive Resume ด้วยตัวเองบนเซิร์ฟเวอร์ของคุณเพื่อควบคุมข้อมูลได้อย่างสมบูรณ์." +#: src/components/resume/store/resume.ts +msgid "Your latest changes could not be saved. Please make sure you are connected to the internet and try again." +msgstr "" + #: src/routes/auth/reset-password.tsx msgid "Your password has been reset successfully. You can now sign in with your new password." msgstr "รหัสผ่านของคุณถูกรีเซ็ตเรียบร้อยแล้ว ตอนนี้คุณสามารถเข้าสู่ระบบด้วยรหัสผ่านใหม่ของคุณได้." @@ -3244,4 +3248,3 @@ msgstr "ซูมออก" #: src/utils/locale.ts msgid "Zulu" msgstr "ซูลู" - diff --git a/locales/tr-TR.po b/locales/tr-TR.po index 30c5eba97..31cefaeb6 100644 --- a/locales/tr-TR.po +++ b/locales/tr-TR.po @@ -3186,6 +3186,10 @@ msgstr "Verileriniz yerel olarak depolanıyor" msgid "Your data is stored securely and is never shared with third parties. You can also self-host Reactive Resume on your own servers for complete control over your data." msgstr "Verileriniz güvenli bir şekilde saklanır ve asla üçüncü partilerle paylaşılmaz. Kendi sunucularınızda Reactive Resume'u barındırarak verileriniz üzerinde tam kontrol sahibi olabilirsiniz." +#: src/components/resume/store/resume.ts +msgid "Your latest changes could not be saved. Please make sure you are connected to the internet and try again." +msgstr "" + #: src/routes/auth/reset-password.tsx msgid "Your password has been reset successfully. You can now sign in with your new password." msgstr "Şifreniz başarıyla sıfırlandı. Artık yeni şifrenizle giriş yapabilirsiniz." @@ -3244,4 +3248,3 @@ msgstr "Uzaklaştır" #: src/utils/locale.ts msgid "Zulu" msgstr "Zulu" - diff --git a/locales/uk-UA.po b/locales/uk-UA.po index 1feec686c..0a4f7a80a 100644 --- a/locales/uk-UA.po +++ b/locales/uk-UA.po @@ -3186,6 +3186,10 @@ msgstr "Ваші дані зберігаються локально" msgid "Your data is stored securely and is never shared with third parties. You can also self-host Reactive Resume on your own servers for complete control over your data." msgstr "Ваші дані зберігаються безпечно і ніколи не передаються третім особам. Ви також можете самостійно розгорнути Reactive Resume на власних серверах для повного контролю над своїми даними." +#: src/components/resume/store/resume.ts +msgid "Your latest changes could not be saved. Please make sure you are connected to the internet and try again." +msgstr "" + #: src/routes/auth/reset-password.tsx msgid "Your password has been reset successfully. You can now sign in with your new password." msgstr "Ваш пароль було успішно скинуто. Тепер ви можете увійти з новим паролем." @@ -3244,4 +3248,3 @@ msgstr "Зменшити" #: src/utils/locale.ts msgid "Zulu" msgstr "Зулу" - diff --git a/locales/uz-UZ.po b/locales/uz-UZ.po index 8ce107c35..5164c254d 100644 --- a/locales/uz-UZ.po +++ b/locales/uz-UZ.po @@ -3186,6 +3186,10 @@ msgstr "Maʼlumotlaringiz lokal tarzda saqlanadi" msgid "Your data is stored securely and is never shared with third parties. You can also self-host Reactive Resume on your own servers for complete control over your data." msgstr "Maʼlumotlaringiz xavfsiz saqlanadi va uchinchi tomonlarga hech qachon ulashilmaydi. Shuningdek, rezyume qurilmasini o‘z serverlaringizda mustaqil joylashtirishingiz va maʼlumotlaringiz ustidan to‘liq nazorat qilishingiz mumkin." +#: src/components/resume/store/resume.ts +msgid "Your latest changes could not be saved. Please make sure you are connected to the internet and try again." +msgstr "" + #: src/routes/auth/reset-password.tsx msgid "Your password has been reset successfully. You can now sign in with your new password." msgstr "Parolingiz muvaffaqiyatli tiklandi. Endi yangi parolingiz bilan tizimga kirishingiz mumkin." @@ -3244,4 +3248,3 @@ msgstr "Kichraytirish" #: src/utils/locale.ts msgid "Zulu" msgstr "Zulu" - diff --git a/locales/vi-VN.po b/locales/vi-VN.po index 08676a5a8..6b087be9f 100644 --- a/locales/vi-VN.po +++ b/locales/vi-VN.po @@ -3186,6 +3186,10 @@ msgstr "Dữ liệu của bạn được lưu trữ cục bộ" msgid "Your data is stored securely and is never shared with third parties. You can also self-host Reactive Resume on your own servers for complete control over your data." msgstr "Dữ liệu của bạn được lưu trữ an toàn và không bao giờ chia sẻ với bên thứ ba. Bạn cũng có thể tự host Reactive Resume trên máy chủ của riêng mình để kiểm soát hoàn toàn dữ liệu của mình." +#: src/components/resume/store/resume.ts +msgid "Your latest changes could not be saved. Please make sure you are connected to the internet and try again." +msgstr "" + #: src/routes/auth/reset-password.tsx msgid "Your password has been reset successfully. You can now sign in with your new password." msgstr "Mật khẩu của bạn đã được đặt lại thành công. Bây giờ bạn có thể đăng nhập bằng mật khẩu mới." @@ -3244,4 +3248,3 @@ msgstr "Thu nhỏ" #: src/utils/locale.ts msgid "Zulu" msgstr "Zulu" - diff --git a/locales/zh-CN.po b/locales/zh-CN.po index baf9d2183..b622dd6ff 100644 --- a/locales/zh-CN.po +++ b/locales/zh-CN.po @@ -3186,6 +3186,10 @@ msgstr "你的数据将存储在本地" msgid "Your data is stored securely and is never shared with third parties. You can also self-host Reactive Resume on your own servers for complete control over your data." msgstr "你的数据会被安全存储,且绝不会与第三方共享。你也可以在自己的服务器上自托管 Reactive Resume,以完全掌控你的数据。" +#: src/components/resume/store/resume.ts +msgid "Your latest changes could not be saved. Please make sure you are connected to the internet and try again." +msgstr "" + #: src/routes/auth/reset-password.tsx msgid "Your password has been reset successfully. You can now sign in with your new password." msgstr "你的密码已成功重置。你现在可以使用新密码登录。" @@ -3244,4 +3248,3 @@ msgstr "缩小" #: src/utils/locale.ts msgid "Zulu" msgstr "祖鲁语" - diff --git a/locales/zh-TW.po b/locales/zh-TW.po index 0b779ae8d..2afc83d4a 100644 --- a/locales/zh-TW.po +++ b/locales/zh-TW.po @@ -3186,6 +3186,10 @@ msgstr "您的資料將儲存在本機裝置中" msgid "Your data is stored securely and is never shared with third parties. You can also self-host Reactive Resume on your own servers for complete control over your data." msgstr "您的資料會被安全保存,且絕不會分享給第三方。您也可以在自有伺服器上自行託管 Reactive Resume,完全掌控自己的資料。" +#: src/components/resume/store/resume.ts +msgid "Your latest changes could not be saved. Please make sure you are connected to the internet and try again." +msgstr "" + #: src/routes/auth/reset-password.tsx msgid "Your password has been reset successfully. You can now sign in with your new password." msgstr "您的密碼已成功重設。您現在可以使用新密碼登入。" @@ -3244,4 +3248,3 @@ msgstr "縮小" #: src/utils/locale.ts msgid "Zulu" msgstr "祖魯語" - diff --git a/locales/zu-ZA.po b/locales/zu-ZA.po index cdb01a03c..8d7a54f6f 100644 --- a/locales/zu-ZA.po +++ b/locales/zu-ZA.po @@ -3181,6 +3181,10 @@ msgstr "" msgid "Your data is stored securely and is never shared with third parties. You can also self-host Reactive Resume on your own servers for complete control over your data." msgstr "" +#: src/components/resume/store/resume.ts +msgid "Your latest changes could not be saved. Please make sure you are connected to the internet and try again." +msgstr "" + #: src/routes/auth/reset-password.tsx msgid "Your password has been reset successfully. You can now sign in with your new password." msgstr "" diff --git a/migrations/20260401210024_silky_the_fallen/migration.sql b/migrations/20260401210024_silky_the_fallen/migration.sql new file mode 100644 index 000000000..c58310594 --- /dev/null +++ b/migrations/20260401210024_silky_the_fallen/migration.sql @@ -0,0 +1,5 @@ +ALTER TABLE "session" ADD COLUMN "impersonated_by" text;--> statement-breakpoint +ALTER TABLE "user" ADD COLUMN "role" text DEFAULT 'user';--> statement-breakpoint +ALTER TABLE "user" ADD COLUMN "banned" boolean DEFAULT false;--> statement-breakpoint +ALTER TABLE "user" ADD COLUMN "ban_reason" text;--> statement-breakpoint +ALTER TABLE "user" ADD COLUMN "ban_expires" timestamp(6) with time zone; \ No newline at end of file diff --git a/migrations/20260401210024_silky_the_fallen/snapshot.json b/migrations/20260401210024_silky_the_fallen/snapshot.json new file mode 100644 index 000000000..90e147bde --- /dev/null +++ b/migrations/20260401210024_silky_the_fallen/snapshot.json @@ -0,0 +1,3114 @@ +{ + "version": "8", + "dialect": "postgres", + "id": "5c375a76-885b-41e3-947e-a6b9fc84725c", + "prevIds": ["35e0fff4-f296-413c-8ab2-18fc66146d8f"], + "ddl": [ + { + "isRlsEnabled": false, + "name": "account", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "apikey", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "jwks", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "oauth_access_token", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "oauth_client", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "oauth_consent", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "oauth_refresh_token", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "passkey", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "resume", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "resume_statistics", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "session", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "two_factor", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "user", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "verification", + "entityType": "tables", + "schema": "public" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "account" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "account_id", + "entityType": "columns", + "schema": "public", + "table": "account" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'credential'", + "generated": null, + "identity": null, + "name": "provider_id", + "entityType": "columns", + "schema": "public", + "table": "account" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "account" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "scope", + "entityType": "columns", + "schema": "public", + "table": "account" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id_token", + "entityType": "columns", + "schema": "public", + "table": "account" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "password", + "entityType": "columns", + "schema": "public", + "table": "account" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "access_token", + "entityType": "columns", + "schema": "public", + "table": "account" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "refresh_token", + "entityType": "columns", + "schema": "public", + "table": "account" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "access_token_expires_at", + "entityType": "columns", + "schema": "public", + "table": "account" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "refresh_token_expires_at", + "entityType": "columns", + "schema": "public", + "table": "account" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "account" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "account" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "apikey" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "apikey" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "start", + "entityType": "columns", + "schema": "public", + "table": "apikey" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "prefix", + "entityType": "columns", + "schema": "public", + "table": "apikey" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "key", + "entityType": "columns", + "schema": "public", + "table": "apikey" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'default'", + "generated": null, + "identity": null, + "name": "config_id", + "entityType": "columns", + "schema": "public", + "table": "apikey" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "reference_id", + "entityType": "columns", + "schema": "public", + "table": "apikey" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "refill_interval", + "entityType": "columns", + "schema": "public", + "table": "apikey" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "refill_amount", + "entityType": "columns", + "schema": "public", + "table": "apikey" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "last_refill_at", + "entityType": "columns", + "schema": "public", + "table": "apikey" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "true", + "generated": null, + "identity": null, + "name": "enabled", + "entityType": "columns", + "schema": "public", + "table": "apikey" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "rate_limit_enabled", + "entityType": "columns", + "schema": "public", + "table": "apikey" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": "86400000", + "generated": null, + "identity": null, + "name": "rate_limit_time_window", + "entityType": "columns", + "schema": "public", + "table": "apikey" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": "10", + "generated": null, + "identity": null, + "name": "rate_limit_max", + "entityType": "columns", + "schema": "public", + "table": "apikey" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "0", + "generated": null, + "identity": null, + "name": "request_count", + "entityType": "columns", + "schema": "public", + "table": "apikey" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "remaining", + "entityType": "columns", + "schema": "public", + "table": "apikey" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "last_request", + "entityType": "columns", + "schema": "public", + "table": "apikey" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "expires_at", + "entityType": "columns", + "schema": "public", + "table": "apikey" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "apikey" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "apikey" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "permissions", + "entityType": "columns", + "schema": "public", + "table": "apikey" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "metadata", + "entityType": "columns", + "schema": "public", + "table": "apikey" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "jwks" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "public_key", + "entityType": "columns", + "schema": "public", + "table": "jwks" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "private_key", + "entityType": "columns", + "schema": "public", + "table": "jwks" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "jwks" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "expires_at", + "entityType": "columns", + "schema": "public", + "table": "jwks" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "oauth_access_token" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "token", + "entityType": "columns", + "schema": "public", + "table": "oauth_access_token" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "client_id", + "entityType": "columns", + "schema": "public", + "table": "oauth_access_token" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "session_id", + "entityType": "columns", + "schema": "public", + "table": "oauth_access_token" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "oauth_access_token" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "reference_id", + "entityType": "columns", + "schema": "public", + "table": "oauth_access_token" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "refresh_id", + "entityType": "columns", + "schema": "public", + "table": "oauth_access_token" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "expires_at", + "entityType": "columns", + "schema": "public", + "table": "oauth_access_token" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "oauth_access_token" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 1, + "default": null, + "generated": null, + "identity": null, + "name": "scopes", + "entityType": "columns", + "schema": "public", + "table": "oauth_access_token" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "oauth_client" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "client_id", + "entityType": "columns", + "schema": "public", + "table": "oauth_client" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "client_secret", + "entityType": "columns", + "schema": "public", + "table": "oauth_client" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "disabled", + "entityType": "columns", + "schema": "public", + "table": "oauth_client" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "skip_consent", + "entityType": "columns", + "schema": "public", + "table": "oauth_client" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "enable_end_session", + "entityType": "columns", + "schema": "public", + "table": "oauth_client" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "subject_type", + "entityType": "columns", + "schema": "public", + "table": "oauth_client" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 1, + "default": null, + "generated": null, + "identity": null, + "name": "scopes", + "entityType": "columns", + "schema": "public", + "table": "oauth_client" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "oauth_client" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "oauth_client" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "oauth_client" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "oauth_client" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "uri", + "entityType": "columns", + "schema": "public", + "table": "oauth_client" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "icon", + "entityType": "columns", + "schema": "public", + "table": "oauth_client" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 1, + "default": null, + "generated": null, + "identity": null, + "name": "contacts", + "entityType": "columns", + "schema": "public", + "table": "oauth_client" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "tos", + "entityType": "columns", + "schema": "public", + "table": "oauth_client" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "policy", + "entityType": "columns", + "schema": "public", + "table": "oauth_client" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "software_id", + "entityType": "columns", + "schema": "public", + "table": "oauth_client" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "software_version", + "entityType": "columns", + "schema": "public", + "table": "oauth_client" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "software_statement", + "entityType": "columns", + "schema": "public", + "table": "oauth_client" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 1, + "default": null, + "generated": null, + "identity": null, + "name": "redirect_uris", + "entityType": "columns", + "schema": "public", + "table": "oauth_client" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 1, + "default": null, + "generated": null, + "identity": null, + "name": "post_logout_redirect_uris", + "entityType": "columns", + "schema": "public", + "table": "oauth_client" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "token_endpoint_auth_method", + "entityType": "columns", + "schema": "public", + "table": "oauth_client" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 1, + "default": null, + "generated": null, + "identity": null, + "name": "grant_types", + "entityType": "columns", + "schema": "public", + "table": "oauth_client" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 1, + "default": null, + "generated": null, + "identity": null, + "name": "response_types", + "entityType": "columns", + "schema": "public", + "table": "oauth_client" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "public", + "entityType": "columns", + "schema": "public", + "table": "oauth_client" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "type", + "entityType": "columns", + "schema": "public", + "table": "oauth_client" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "require_pkce", + "entityType": "columns", + "schema": "public", + "table": "oauth_client" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "reference_id", + "entityType": "columns", + "schema": "public", + "table": "oauth_client" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "metadata", + "entityType": "columns", + "schema": "public", + "table": "oauth_client" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "oauth_consent" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "client_id", + "entityType": "columns", + "schema": "public", + "table": "oauth_consent" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "oauth_consent" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "reference_id", + "entityType": "columns", + "schema": "public", + "table": "oauth_consent" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 1, + "default": null, + "generated": null, + "identity": null, + "name": "scopes", + "entityType": "columns", + "schema": "public", + "table": "oauth_consent" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "oauth_consent" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "oauth_consent" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "oauth_refresh_token" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "token", + "entityType": "columns", + "schema": "public", + "table": "oauth_refresh_token" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "client_id", + "entityType": "columns", + "schema": "public", + "table": "oauth_refresh_token" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "session_id", + "entityType": "columns", + "schema": "public", + "table": "oauth_refresh_token" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "oauth_refresh_token" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "reference_id", + "entityType": "columns", + "schema": "public", + "table": "oauth_refresh_token" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "expires_at", + "entityType": "columns", + "schema": "public", + "table": "oauth_refresh_token" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "oauth_refresh_token" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "revoked", + "entityType": "columns", + "schema": "public", + "table": "oauth_refresh_token" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "auth_time", + "entityType": "columns", + "schema": "public", + "table": "oauth_refresh_token" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 1, + "default": null, + "generated": null, + "identity": null, + "name": "scopes", + "entityType": "columns", + "schema": "public", + "table": "oauth_refresh_token" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "passkey" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "passkey" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "aaguid", + "entityType": "columns", + "schema": "public", + "table": "passkey" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "public_key", + "entityType": "columns", + "schema": "public", + "table": "passkey" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "credential_id", + "entityType": "columns", + "schema": "public", + "table": "passkey" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "counter", + "entityType": "columns", + "schema": "public", + "table": "passkey" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "device_type", + "entityType": "columns", + "schema": "public", + "table": "passkey" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "backed_up", + "entityType": "columns", + "schema": "public", + "table": "passkey" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "transports", + "entityType": "columns", + "schema": "public", + "table": "passkey" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "passkey" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "passkey" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "passkey" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "resume" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "resume" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "slug", + "entityType": "columns", + "schema": "public", + "table": "resume" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 1, + "default": "'{}'", + "generated": null, + "identity": null, + "name": "tags", + "entityType": "columns", + "schema": "public", + "table": "resume" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "is_public", + "entityType": "columns", + "schema": "public", + "table": "resume" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "is_locked", + "entityType": "columns", + "schema": "public", + "table": "resume" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "password", + "entityType": "columns", + "schema": "public", + "table": "resume" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "data", + "entityType": "columns", + "schema": "public", + "table": "resume" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "resume" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "resume" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "resume" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "resume_statistics" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "0", + "generated": null, + "identity": null, + "name": "views", + "entityType": "columns", + "schema": "public", + "table": "resume_statistics" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "0", + "generated": null, + "identity": null, + "name": "downloads", + "entityType": "columns", + "schema": "public", + "table": "resume_statistics" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "last_viewed_at", + "entityType": "columns", + "schema": "public", + "table": "resume_statistics" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "last_downloaded_at", + "entityType": "columns", + "schema": "public", + "table": "resume_statistics" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "resume_id", + "entityType": "columns", + "schema": "public", + "table": "resume_statistics" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "resume_statistics" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "resume_statistics" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "session" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "token", + "entityType": "columns", + "schema": "public", + "table": "session" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "ip_address", + "entityType": "columns", + "schema": "public", + "table": "session" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_agent", + "entityType": "columns", + "schema": "public", + "table": "session" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "impersonated_by", + "entityType": "columns", + "schema": "public", + "table": "session" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "session" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "expires_at", + "entityType": "columns", + "schema": "public", + "table": "session" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "session" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "session" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "two_factor" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "two_factor" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "secret", + "entityType": "columns", + "schema": "public", + "table": "two_factor" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "backup_codes", + "entityType": "columns", + "schema": "public", + "table": "two_factor" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "two_factor" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "two_factor" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "user" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "image", + "entityType": "columns", + "schema": "public", + "table": "user" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "user" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "email", + "entityType": "columns", + "schema": "public", + "table": "user" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "email_verified", + "entityType": "columns", + "schema": "public", + "table": "user" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "username", + "entityType": "columns", + "schema": "public", + "table": "user" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "display_username", + "entityType": "columns", + "schema": "public", + "table": "user" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "two_factor_enabled", + "entityType": "columns", + "schema": "public", + "table": "user" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "last_active_at", + "entityType": "columns", + "schema": "public", + "table": "user" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": "'user'", + "generated": null, + "identity": null, + "name": "role", + "entityType": "columns", + "schema": "public", + "table": "user" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "banned", + "entityType": "columns", + "schema": "public", + "table": "user" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "ban_reason", + "entityType": "columns", + "schema": "public", + "table": "user" + }, + { + "type": "timestamp(6) with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "ban_expires", + "entityType": "columns", + "schema": "public", + "table": "user" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "user" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "user" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "verification" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "identifier", + "entityType": "columns", + "schema": "public", + "table": "verification" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "value", + "entityType": "columns", + "schema": "public", + "table": "verification" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "expires_at", + "entityType": "columns", + "schema": "public", + "table": "verification" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "verification" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "verification" + }, + { + "nameExplicit": false, + "columns": [ + { + "value": "user_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "account_user_id_index", + "entityType": "indexes", + "schema": "public", + "table": "account" + }, + { + "nameExplicit": false, + "columns": [ + { + "value": "reference_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "apikey_user_id_index", + "entityType": "indexes", + "schema": "public", + "table": "apikey" + }, + { + "nameExplicit": false, + "columns": [ + { + "value": "key", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "apikey_key_index", + "entityType": "indexes", + "schema": "public", + "table": "apikey" + }, + { + "nameExplicit": false, + "columns": [ + { + "value": "config_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "apikey_config_id_index", + "entityType": "indexes", + "schema": "public", + "table": "apikey" + }, + { + "nameExplicit": false, + "columns": [ + { + "value": "enabled", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "reference_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "apikey_enabled_user_id_index", + "entityType": "indexes", + "schema": "public", + "table": "apikey" + }, + { + "nameExplicit": false, + "columns": [ + { + "value": "token", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "oauth_access_token_token_index", + "entityType": "indexes", + "schema": "public", + "table": "oauth_access_token" + }, + { + "nameExplicit": false, + "columns": [ + { + "value": "client_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "oauth_client_client_id_index", + "entityType": "indexes", + "schema": "public", + "table": "oauth_client" + }, + { + "nameExplicit": false, + "columns": [ + { + "value": "user_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "client_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "oauth_consent_user_id_client_id_index", + "entityType": "indexes", + "schema": "public", + "table": "oauth_consent" + }, + { + "nameExplicit": false, + "columns": [ + { + "value": "token", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "oauth_refresh_token_token_index", + "entityType": "indexes", + "schema": "public", + "table": "oauth_refresh_token" + }, + { + "nameExplicit": false, + "columns": [ + { + "value": "user_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "passkey_user_id_index", + "entityType": "indexes", + "schema": "public", + "table": "passkey" + }, + { + "nameExplicit": false, + "columns": [ + { + "value": "user_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "resume_user_id_index", + "entityType": "indexes", + "schema": "public", + "table": "resume" + }, + { + "nameExplicit": false, + "columns": [ + { + "value": "created_at", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "resume_created_at_index", + "entityType": "indexes", + "schema": "public", + "table": "resume" + }, + { + "nameExplicit": false, + "columns": [ + { + "value": "user_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "updated_at", + "isExpression": false, + "asc": false, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "resume_user_id_updated_at_index", + "entityType": "indexes", + "schema": "public", + "table": "resume" + }, + { + "nameExplicit": false, + "columns": [ + { + "value": "is_public", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "slug", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "user_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "resume_is_public_slug_user_id_index", + "entityType": "indexes", + "schema": "public", + "table": "resume" + }, + { + "nameExplicit": false, + "columns": [ + { + "value": "token", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "user_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "session_token_user_id_index", + "entityType": "indexes", + "schema": "public", + "table": "session" + }, + { + "nameExplicit": false, + "columns": [ + { + "value": "expires_at", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "session_expires_at_index", + "entityType": "indexes", + "schema": "public", + "table": "session" + }, + { + "nameExplicit": false, + "columns": [ + { + "value": "user_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "two_factor_user_id_index", + "entityType": "indexes", + "schema": "public", + "table": "two_factor" + }, + { + "nameExplicit": false, + "columns": [ + { + "value": "secret", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "two_factor_secret_index", + "entityType": "indexes", + "schema": "public", + "table": "two_factor" + }, + { + "nameExplicit": false, + "columns": [ + { + "value": "created_at", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "user_created_at_index", + "entityType": "indexes", + "schema": "public", + "table": "user" + }, + { + "nameExplicit": false, + "columns": [ + { + "value": "identifier", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "verification_identifier_index", + "entityType": "indexes", + "schema": "public", + "table": "verification" + }, + { + "nameExplicit": false, + "columns": ["user_id"], + "schemaTo": "public", + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "account_user_id_user_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "account" + }, + { + "nameExplicit": false, + "columns": ["reference_id"], + "schemaTo": "public", + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "apikey_user_id_user_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "apikey" + }, + { + "nameExplicit": false, + "columns": ["client_id"], + "schemaTo": "public", + "tableTo": "oauth_client", + "columnsTo": ["client_id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "oauth_access_token_client_id_oauth_client_client_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "oauth_access_token" + }, + { + "nameExplicit": false, + "columns": ["session_id"], + "schemaTo": "public", + "tableTo": "session", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "oauth_access_token_session_id_session_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "oauth_access_token" + }, + { + "nameExplicit": false, + "columns": ["user_id"], + "schemaTo": "public", + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "oauth_access_token_user_id_user_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "oauth_access_token" + }, + { + "nameExplicit": false, + "columns": ["refresh_id"], + "schemaTo": "public", + "tableTo": "oauth_refresh_token", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "oauth_access_token_refresh_id_oauth_refresh_token_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "oauth_access_token" + }, + { + "nameExplicit": false, + "columns": ["user_id"], + "schemaTo": "public", + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "oauth_client_user_id_user_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "oauth_client" + }, + { + "nameExplicit": false, + "columns": ["client_id"], + "schemaTo": "public", + "tableTo": "oauth_client", + "columnsTo": ["client_id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "oauth_consent_client_id_oauth_client_client_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "oauth_consent" + }, + { + "nameExplicit": false, + "columns": ["user_id"], + "schemaTo": "public", + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "oauth_consent_user_id_user_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "oauth_consent" + }, + { + "nameExplicit": false, + "columns": ["client_id"], + "schemaTo": "public", + "tableTo": "oauth_client", + "columnsTo": ["client_id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "oauth_refresh_token_client_id_oauth_client_client_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "oauth_refresh_token" + }, + { + "nameExplicit": false, + "columns": ["session_id"], + "schemaTo": "public", + "tableTo": "session", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "oauth_refresh_token_session_id_session_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "oauth_refresh_token" + }, + { + "nameExplicit": false, + "columns": ["user_id"], + "schemaTo": "public", + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "oauth_refresh_token_user_id_user_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "oauth_refresh_token" + }, + { + "nameExplicit": false, + "columns": ["user_id"], + "schemaTo": "public", + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "passkey_user_id_user_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "passkey" + }, + { + "nameExplicit": false, + "columns": ["user_id"], + "schemaTo": "public", + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "resume_user_id_user_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "resume" + }, + { + "nameExplicit": false, + "columns": ["resume_id"], + "schemaTo": "public", + "tableTo": "resume", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "resume_statistics_resume_id_resume_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "resume_statistics" + }, + { + "nameExplicit": false, + "columns": ["user_id"], + "schemaTo": "public", + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "session_user_id_user_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "session" + }, + { + "nameExplicit": false, + "columns": ["user_id"], + "schemaTo": "public", + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "two_factor_user_id_user_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "two_factor" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "account_pkey", + "schema": "public", + "table": "account", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "apikey_pkey", + "schema": "public", + "table": "apikey", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "jwks_pkey", + "schema": "public", + "table": "jwks", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "oauth_access_token_pkey", + "schema": "public", + "table": "oauth_access_token", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "oauth_client_pkey", + "schema": "public", + "table": "oauth_client", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "oauth_consent_pkey", + "schema": "public", + "table": "oauth_consent", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "oauth_refresh_token_pkey", + "schema": "public", + "table": "oauth_refresh_token", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "passkey_pkey", + "schema": "public", + "table": "passkey", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "resume_pkey", + "schema": "public", + "table": "resume", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "resume_statistics_pkey", + "schema": "public", + "table": "resume_statistics", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "session_pkey", + "schema": "public", + "table": "session", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "two_factor_pkey", + "schema": "public", + "table": "two_factor", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "user_pkey", + "schema": "public", + "table": "user", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "verification_pkey", + "schema": "public", + "table": "verification", + "entityType": "pks" + }, + { + "nameExplicit": false, + "columns": ["slug", "user_id"], + "nullsNotDistinct": false, + "name": "resume_slug_user_id_unique", + "entityType": "uniques", + "schema": "public", + "table": "resume" + }, + { + "nameExplicit": false, + "columns": ["token"], + "nullsNotDistinct": false, + "name": "oauth_access_token_token_key", + "schema": "public", + "table": "oauth_access_token", + "entityType": "uniques" + }, + { + "nameExplicit": false, + "columns": ["client_id"], + "nullsNotDistinct": false, + "name": "oauth_client_client_id_key", + "schema": "public", + "table": "oauth_client", + "entityType": "uniques" + }, + { + "nameExplicit": false, + "columns": ["resume_id"], + "nullsNotDistinct": false, + "name": "resume_statistics_resume_id_key", + "schema": "public", + "table": "resume_statistics", + "entityType": "uniques" + }, + { + "nameExplicit": false, + "columns": ["token"], + "nullsNotDistinct": false, + "name": "session_token_key", + "schema": "public", + "table": "session", + "entityType": "uniques" + }, + { + "nameExplicit": false, + "columns": ["email"], + "nullsNotDistinct": false, + "name": "user_email_key", + "schema": "public", + "table": "user", + "entityType": "uniques" + }, + { + "nameExplicit": false, + "columns": ["username"], + "nullsNotDistinct": false, + "name": "user_username_key", + "schema": "public", + "table": "user", + "entityType": "uniques" + }, + { + "nameExplicit": false, + "columns": ["display_username"], + "nullsNotDistinct": false, + "name": "user_display_username_key", + "schema": "public", + "table": "user", + "entityType": "uniques" + }, + { + "nameExplicit": false, + "columns": ["identifier"], + "nullsNotDistinct": false, + "name": "verification_identifier_key", + "schema": "public", + "table": "verification", + "entityType": "uniques" + } + ], + "renames": [] +} diff --git a/package.json b/package.json index 416d55b6c..5ae16e980 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "reactive-resume", - "version": "5.0.14", + "version": "5.0.15", "description": "Reactive Resume is a free and open-source resume builder that simplifies the process of creating, updating, and sharing your resume.", "license": "MIT", "author": { @@ -31,13 +31,14 @@ "preview": "vp preview", "start": "node .output/server/index.mjs", "test": "vp test", + "test:coverage": "vp test --coverage --run", "typecheck": "tsgo --noEmit" }, "dependencies": { "@ai-sdk/anthropic": "^3.0.64", - "@ai-sdk/google": "^3.0.53", - "@ai-sdk/openai": "^3.0.48", - "@aws-sdk/client-s3": "^3.1019.0", + "@ai-sdk/google": "^3.0.55", + "@ai-sdk/openai": "^3.0.49", + "@aws-sdk/client-s3": "^3.1022.0", "@base-ui/react": "^1.3.0", "@better-auth/api-key": "^1.5.6", "@better-auth/drizzle-adapter": "^1.5.6", @@ -50,7 +51,7 @@ "@hookform/resolvers": "^5.2.2", "@lingui/core": "^5.9.4", "@lingui/react": "^5.9.4", - "@modelcontextprotocol/sdk": "^1.28.0", + "@modelcontextprotocol/sdk": "^1.29.0", "@monaco-editor/react": "4.8.0-rc.3", "@orpc/client": "^1.13.13", "@orpc/json-schema": "^1.13.13", @@ -62,20 +63,20 @@ "@phosphor-icons/web": "^2.1.2", "@sindresorhus/slugify": "^3.0.0", "@t3-oss/env-core": "^0.13.11", - "@tanstack/react-query": "^5.95.2", - "@tanstack/react-router": "^1.168.8", + "@tanstack/react-query": "^5.96.1", + "@tanstack/react-router": "^1.168.10", "@tanstack/react-router-ssr-query": "^1.166.10", - "@tanstack/react-start": "^1.167.13", + "@tanstack/react-start": "^1.167.16", "@tanstack/zod-adapter": "^1.166.9", - "@tiptap/extension-highlight": "^3.21.0", - "@tiptap/extension-table": "^3.21.0", - "@tiptap/extension-text-align": "^3.21.0", - "@tiptap/pm": "^3.21.0", - "@tiptap/react": "^3.21.0", - "@tiptap/starter-kit": "^3.21.0", + "@tiptap/extension-highlight": "^3.22.0", + "@tiptap/extension-table": "^3.22.0", + "@tiptap/extension-text-align": "^3.22.0", + "@tiptap/pm": "^3.22.0", + "@tiptap/react": "^3.22.0", + "@tiptap/starter-kit": "^3.22.0", "@uiw/color-convert": "^2.9.6", "@uiw/react-color-colorful": "^2.9.6", - "ai": "^6.0.141", + "ai": "^6.0.142", "ai-sdk-ollama": "^3.8.2", "bcrypt": "^6.0.0", "better-auth": "^1.5.6", @@ -106,10 +107,10 @@ "react-resizable-panels": "^4.8.0", "react-window": "^2.2.7", "react-zoom-pan-pinch": "^3.7.0", - "shadcn": "^4.1.1", + "shadcn": "^4.1.2", "sharp": "^0.34.5", "sonner": "^2.0.7", - "srvx": "^0.11.13", + "srvx": "^0.11.14", "tailwind-merge": "^3.5.0", "tailwindcss": "^4.2.2", "ts-pattern": "^5.9.0", @@ -129,6 +130,7 @@ "@lingui/vite-plugin": "^5.9.4", "@rolldown/plugin-babel": "^0.2.2", "@tailwindcss/vite": "^4.2.2", + "@testing-library/react": "^16.3.2", "@types/bcrypt": "^6.0.0", "@types/js-cookie": "^3.0.6", "@types/node": "^25.5.0", @@ -136,24 +138,24 @@ "@types/pg": "^8.20.0", "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", - "@typescript/native-preview": "7.0.0-dev.20260329.1", + "@typescript/native-preview": "7.0.0-dev.20260401.1", "@vitejs/plugin-react": "^6.0.1", + "@vitest/coverage-v8": "^4.1.2", "babel-plugin-macros": "^3.1.0", "drizzle-kit": "1.0.0-beta.20", + "happy-dom": "^20.8.9", "jose": "^6.2.2", - "jsdom": "^29.0.1", - "knip": "^6.1.0", + "knip": "^6.2.0", "nitro": "3.0.260311-beta", "node-addon-api": "^8.7.0", "node-gyp": "^12.2.0", - "npm-check-updates": "^19.6.6", - "tsx": "^4.21.0", + "npm-check-updates": "^20.0.0", "vite": "npm:@voidzero-dev/vite-plus-core@latest", "vite-plugin-pwa": "^1.2.0", "vite-plus": "latest", "vitest": "npm:@voidzero-dev/vite-plus-test@latest" }, - "packageManager": "pnpm@10.33.0", + "packageManager": "pnpm@10.33.0+sha512.10568bb4a6afb58c9eb3630da90cc9516417abebd3fabbe6739f0ae795728da1491e9db5a544c76ad8eb7570f5c4bb3d6c637b2cb41bfdcdb47fa823c8649319", "pnpm": { "overrides": { "@isaacs/brace-expansion": "^5.0.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 282e52f7e..f3b6a111d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -26,29 +26,29 @@ importers: specifier: ^3.0.64 version: 3.0.64(zod@4.3.6) '@ai-sdk/google': - specifier: ^3.0.53 - version: 3.0.53(zod@4.3.6) + specifier: ^3.0.55 + version: 3.0.55(zod@4.3.6) '@ai-sdk/openai': - specifier: ^3.0.48 - version: 3.0.48(zod@4.3.6) + specifier: ^3.0.49 + version: 3.0.49(zod@4.3.6) '@aws-sdk/client-s3': - specifier: ^3.1019.0 - version: 3.1019.0 + specifier: ^3.1022.0 + version: 3.1022.0 '@base-ui/react': specifier: ^1.3.0 version: 1.3.0(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@better-auth/api-key': specifier: ^1.5.6 - version: 1.5.6(@better-auth/core@1.5.6(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.2(zod@4.3.6))(jose@6.2.2)(kysely@0.28.14)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(better-auth@1.5.6(d57c8314811a2c76a791cf28e230a6db)) + version: 1.5.6(@better-auth/core@1.5.6(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.2(zod@4.3.6))(jose@6.2.2)(kysely@0.28.14)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(better-auth@1.5.6(9d65c2ad022ab83f20e12e7f626dac52)) '@better-auth/drizzle-adapter': specifier: ^1.5.6 version: 1.5.6(@better-auth/core@1.5.6(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.2(zod@4.3.6))(jose@6.2.2)(kysely@0.28.14)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(drizzle-orm@1.0.0-beta.20(@electric-sql/pglite@0.3.15)(@opentelemetry/api@1.9.0)(@types/mssql@9.1.9(@azure/core-client@1.10.1))(@types/pg@8.20.0)(mssql@11.0.1(@azure/core-client@1.10.1))(mysql2@3.15.3)(pg@8.20.0)(postgres@3.4.7)(valibot@1.2.0(typescript@5.9.3))(zod@4.3.6)) '@better-auth/infra': specifier: ^0.1.13 - version: 0.1.13(@better-auth/core@1.5.6(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.2(zod@4.3.6))(jose@6.2.2)(kysely@0.28.14)(nanostores@1.1.1))(@better-auth/sso@1.5.5(@better-auth/core@1.5.6(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.2(zod@4.3.6))(jose@6.2.2)(kysely@0.28.14)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(better-auth@1.5.6(d57c8314811a2c76a791cf28e230a6db))(better-call@1.3.2(zod@4.3.6)))(better-auth@1.5.6(d57c8314811a2c76a791cf28e230a6db))(zod@4.3.6) + version: 0.1.13(@better-auth/core@1.5.6(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.2(zod@4.3.6))(jose@6.2.2)(kysely@0.28.14)(nanostores@1.1.1))(@better-auth/sso@1.5.5(@better-auth/core@1.5.6(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.2(zod@4.3.6))(jose@6.2.2)(kysely@0.28.14)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(better-auth@1.5.6(9d65c2ad022ab83f20e12e7f626dac52))(better-call@1.3.2(zod@4.3.6)))(better-auth@1.5.6(9d65c2ad022ab83f20e12e7f626dac52))(zod@4.3.6) '@better-auth/oauth-provider': specifier: ^1.5.6 - version: 1.5.6(@better-auth/core@1.5.6(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.2(zod@4.3.6))(jose@6.2.2)(kysely@0.28.14)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-auth@1.5.6(d57c8314811a2c76a791cf28e230a6db))(better-call@1.3.2(zod@4.3.6)) + version: 1.5.6(@better-auth/core@1.5.6(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.2(zod@4.3.6))(jose@6.2.2)(kysely@0.28.14)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-auth@1.5.6(9d65c2ad022ab83f20e12e7f626dac52))(better-call@1.3.2(zod@4.3.6)) '@dnd-kit/core': specifier: ^6.3.1 version: 6.3.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) @@ -71,8 +71,8 @@ importers: specifier: ^5.9.4 version: 5.9.4(@lingui/babel-plugin-lingui-macro@5.9.4(babel-plugin-macros@3.1.0)(typescript@5.9.3))(babel-plugin-macros@3.1.0)(react@19.2.4) '@modelcontextprotocol/sdk': - specifier: ^1.28.0 - version: 1.28.0(zod@4.3.6) + specifier: ^1.29.0 + version: 1.29.0(zod@4.3.6) '@monaco-editor/react': specifier: 4.8.0-rc.3 version: 4.8.0-rc.3(monaco-editor@0.55.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) @@ -81,19 +81,19 @@ importers: version: 1.13.13(@opentelemetry/api@1.9.0) '@orpc/json-schema': specifier: ^1.13.13 - version: 1.13.13(@opentelemetry/api@1.9.0)(crossws@0.4.4(srvx@0.11.13))(ws@8.19.0) + version: 1.13.13(@opentelemetry/api@1.9.0)(crossws@0.4.4(srvx@0.11.14))(ws@8.19.0) '@orpc/openapi': specifier: ^1.13.13 - version: 1.13.13(@opentelemetry/api@1.9.0)(crossws@0.4.4(srvx@0.11.13))(ws@8.19.0) + version: 1.13.13(@opentelemetry/api@1.9.0)(crossws@0.4.4(srvx@0.11.14))(ws@8.19.0) '@orpc/server': specifier: ^1.13.13 - version: 1.13.13(@opentelemetry/api@1.9.0)(crossws@0.4.4(srvx@0.11.13))(ws@8.19.0) + version: 1.13.13(@opentelemetry/api@1.9.0)(crossws@0.4.4(srvx@0.11.14))(ws@8.19.0) '@orpc/tanstack-query': specifier: ^1.13.13 - version: 1.13.13(@opentelemetry/api@1.9.0)(@orpc/client@1.13.13(@opentelemetry/api@1.9.0))(@tanstack/query-core@5.95.2) + version: 1.13.13(@opentelemetry/api@1.9.0)(@orpc/client@1.13.13(@opentelemetry/api@1.9.0))(@tanstack/query-core@5.96.1) '@orpc/zod': specifier: ^1.13.13 - version: 1.13.13(@opentelemetry/api@1.9.0)(@orpc/contract@1.13.13(@opentelemetry/api@1.9.0))(@orpc/server@1.13.13(@opentelemetry/api@1.9.0)(crossws@0.4.4(srvx@0.11.13))(ws@8.19.0))(crossws@0.4.4(srvx@0.11.13))(ws@8.19.0)(zod@4.3.6) + version: 1.13.13(@opentelemetry/api@1.9.0)(@orpc/contract@1.13.13(@opentelemetry/api@1.9.0))(@orpc/server@1.13.13(@opentelemetry/api@1.9.0)(crossws@0.4.4(srvx@0.11.14))(ws@8.19.0))(crossws@0.4.4(srvx@0.11.14))(ws@8.19.0)(zod@4.3.6) '@phosphor-icons/react': specifier: ^2.1.10 version: 2.1.10(react-dom@19.2.4(react@19.2.4))(react@19.2.4) @@ -107,38 +107,38 @@ importers: specifier: ^0.13.11 version: 0.13.11(typescript@5.9.3)(valibot@1.2.0(typescript@5.9.3))(zod@4.3.6) '@tanstack/react-query': - specifier: ^5.95.2 - version: 5.95.2(react@19.2.4) + specifier: ^5.96.1 + version: 5.96.1(react@19.2.4) '@tanstack/react-router': - specifier: ^1.168.8 - version: 1.168.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + specifier: ^1.168.10 + version: 1.168.10(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@tanstack/react-router-ssr-query': specifier: ^1.166.10 - version: 1.166.10(@tanstack/query-core@5.95.2)(@tanstack/react-query@5.95.2(react@19.2.4))(@tanstack/react-router@1.168.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@tanstack/router-core@1.168.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 1.166.10(@tanstack/query-core@5.96.1)(@tanstack/react-query@5.96.1(react@19.2.4))(@tanstack/react-router@1.168.10(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@tanstack/router-core@1.168.9)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@tanstack/react-start': - specifier: ^1.167.13 - version: 1.167.13(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.3)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(crossws@0.4.4(srvx@0.11.13))(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + specifier: ^1.167.16 + version: 1.167.16(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.3)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(crossws@0.4.4(srvx@0.11.14))(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@tanstack/zod-adapter': specifier: ^1.166.9 - version: 1.166.9(@tanstack/react-router@1.168.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(zod@4.3.6) + version: 1.166.9(@tanstack/react-router@1.168.10(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(zod@4.3.6) '@tiptap/extension-highlight': - specifier: ^3.21.0 - version: 3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0)) + specifier: ^3.22.0 + version: 3.22.0(@tiptap/core@3.22.0(@tiptap/pm@3.22.0)) '@tiptap/extension-table': - specifier: ^3.21.0 - version: 3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))(@tiptap/pm@3.21.0) + specifier: ^3.22.0 + version: 3.22.0(@tiptap/core@3.22.0(@tiptap/pm@3.22.0))(@tiptap/pm@3.22.0) '@tiptap/extension-text-align': - specifier: ^3.21.0 - version: 3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0)) + specifier: ^3.22.0 + version: 3.22.0(@tiptap/core@3.22.0(@tiptap/pm@3.22.0)) '@tiptap/pm': - specifier: ^3.21.0 - version: 3.21.0 + specifier: ^3.22.0 + version: 3.22.0 '@tiptap/react': - specifier: ^3.21.0 - version: 3.21.0(@floating-ui/dom@1.7.6)(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))(@tiptap/pm@3.21.0)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + specifier: ^3.22.0 + version: 3.22.0(@floating-ui/dom@1.7.6)(@tiptap/core@3.22.0(@tiptap/pm@3.22.0))(@tiptap/pm@3.22.0)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@tiptap/starter-kit': - specifier: ^3.21.0 - version: 3.21.0 + specifier: ^3.22.0 + version: 3.22.0 '@uiw/color-convert': specifier: ^2.9.6 version: 2.9.6(@babel/runtime@7.28.6) @@ -146,17 +146,17 @@ importers: specifier: ^2.9.6 version: 2.9.6(@babel/runtime@7.28.6)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) ai: - specifier: ^6.0.141 - version: 6.0.141(zod@4.3.6) + specifier: ^6.0.142 + version: 6.0.142(zod@4.3.6) ai-sdk-ollama: specifier: ^3.8.2 - version: 3.8.2(ai@6.0.141(zod@4.3.6))(zod@4.3.6) + version: 3.8.2(ai@6.0.142(zod@4.3.6))(zod@4.3.6) bcrypt: specifier: ^6.0.0 version: 6.0.0 better-auth: specifier: ^1.5.6 - version: 1.5.6(d57c8314811a2c76a791cf28e230a6db) + version: 1.5.6(9d65c2ad022ab83f20e12e7f626dac52) class-variance-authority: specifier: ^0.7.1 version: 0.7.1 @@ -239,8 +239,8 @@ importers: specifier: ^3.7.0 version: 3.7.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) shadcn: - specifier: ^4.1.1 - version: 4.1.1(@types/node@25.5.0)(babel-plugin-macros@3.1.0)(typescript@5.9.3) + specifier: ^4.1.2 + version: 4.1.2(@types/node@25.5.0)(babel-plugin-macros@3.1.0)(typescript@5.9.3) sharp: specifier: ^0.34.5 version: 0.34.5 @@ -248,8 +248,8 @@ importers: specifier: ^2.0.7 version: 2.0.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4) srvx: - specifier: ^0.11.13 - version: 0.11.13 + specifier: ^0.11.14 + version: 0.11.14 tailwind-merge: specifier: ^3.5.0 version: 3.5.0 @@ -302,6 +302,9 @@ importers: '@tailwindcss/vite': specifier: ^4.2.2 version: 4.2.2(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.3)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)) + '@testing-library/react': + specifier: ^16.3.2 + version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@types/bcrypt': specifier: ^6.0.0 version: 6.0.0 @@ -324,26 +327,29 @@ importers: specifier: ^19.2.3 version: 19.2.3(@types/react@19.2.14) '@typescript/native-preview': - specifier: 7.0.0-dev.20260329.1 - version: 7.0.0-dev.20260329.1 + specifier: 7.0.0-dev.20260401.1 + version: 7.0.0-dev.20260401.1 '@vitejs/plugin-react': specifier: ^6.0.1 version: 6.0.1(@rolldown/plugin-babel@0.2.2(@babel/core@7.29.0)(@babel/runtime@7.28.6)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.3)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(rolldown@1.0.0-rc.9))(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.3)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)) + '@vitest/coverage-v8': + specifier: ^4.1.2 + version: 4.1.2(@voidzero-dev/vite-plus-test@0.1.12(@opentelemetry/api@1.9.0)(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.3)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.3)(happy-dom@20.8.9)(jiti@2.6.1)(jsdom@29.0.1(@noble/hashes@2.0.1))(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)) babel-plugin-macros: specifier: ^3.1.0 version: 3.1.0 drizzle-kit: specifier: 1.0.0-beta.20 version: 1.0.0-beta.20 + happy-dom: + specifier: ^20.8.9 + version: 20.8.9 jose: specifier: ^6.2.2 version: 6.2.2 - jsdom: - specifier: ^29.0.1 - version: 29.0.1(@noble/hashes@2.0.1) knip: - specifier: ^6.1.0 - version: 6.1.0 + specifier: ^6.2.0 + version: 6.2.0 nitro: specifier: 3.0.260311-beta version: 3.0.260311-beta(@azure/identity@4.13.0)(@electric-sql/pglite@0.3.15)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.3)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(chokidar@4.0.3)(dotenv@17.3.1)(drizzle-orm@1.0.0-beta.20(@electric-sql/pglite@0.3.15)(@opentelemetry/api@1.9.0)(@types/mssql@9.1.9(@azure/core-client@1.10.1))(@types/pg@8.20.0)(mssql@11.0.1(@azure/core-client@1.10.1))(mysql2@3.15.3)(pg@8.20.0)(postgres@3.4.7)(valibot@1.2.0(typescript@5.9.3))(zod@4.3.6))(giget@2.0.0)(jiti@2.6.1)(lru-cache@11.2.7)(mongodb@7.1.0(socks@2.8.7))(mysql2@3.15.3)(rollup@2.80.0) @@ -354,11 +360,8 @@ importers: specifier: ^12.2.0 version: 12.2.0 npm-check-updates: - specifier: ^19.6.6 - version: 19.6.6 - tsx: - specifier: ^4.21.0 - version: 4.21.0 + specifier: ^20.0.0 + version: 20.0.0 vite: specifier: npm:@voidzero-dev/vite-plus-core@latest version: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.3)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' @@ -367,10 +370,10 @@ importers: version: 1.2.0(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.3)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(workbox-build@7.4.0(@types/babel__core@7.20.5))(workbox-window@7.4.0) vite-plus: specifier: latest - version: 0.1.12(@opentelemetry/api@1.9.0)(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.3)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.3)(jiti@2.6.1)(jsdom@29.0.1(@noble/hashes@2.0.1))(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2) + version: 0.1.12(@opentelemetry/api@1.9.0)(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.3)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.3)(happy-dom@20.8.9)(jiti@2.6.1)(jsdom@29.0.1(@noble/hashes@2.0.1))(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2) vitest: specifier: npm:@voidzero-dev/vite-plus-test@latest - version: '@voidzero-dev/vite-plus-test@0.1.12(@opentelemetry/api@1.9.0)(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.3)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.3)(jiti@2.6.1)(jsdom@29.0.1(@noble/hashes@2.0.1))(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' + version: '@voidzero-dev/vite-plus-test@0.1.12(@opentelemetry/api@1.9.0)(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.3)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.3)(happy-dom@20.8.9)(jiti@2.6.1)(jsdom@29.0.1(@noble/hashes@2.0.1))(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' packages: @@ -380,20 +383,20 @@ packages: peerDependencies: zod: ^3.25.76 || ^4.1.8 - '@ai-sdk/gateway@3.0.83': - resolution: {integrity: sha512-LvlWujbSdEkTBXBLFtF7GS6riXdHhH0O+DpDrCaNQvXeHmSF2jKsOg7JWXiCgygAHM5cWFAO3JYmZp83DjiuBQ==} + '@ai-sdk/gateway@3.0.84': + resolution: {integrity: sha512-RnUw6UNvkaw9MEaJU9cIjA+WBP+ZR5+M/9nfbfJHcGKtTbcWXijJuYKx9nYRnm+qU+iiakb0XvQA/vvho6lTsw==} engines: {node: '>=18'} peerDependencies: zod: ^3.25.76 || ^4.1.8 - '@ai-sdk/google@3.0.53': - resolution: {integrity: sha512-uz8tIlkDgQJG9Js2Wh9JHzd4kI9+hYJqf9XXJLx60vyN5mRIqhr49iwR5zGP5Gl8odp2PeR3Gh2k+5bh3Z1HHw==} + '@ai-sdk/google@3.0.55': + resolution: {integrity: sha512-lIID7kDdeqzSR/eg+LrEFJGoe/mIwuW4eBdjS84Bi0cIY4cmGwSsvwYILSdiad0197Ha+DUbCxOUuSOnORUx7g==} engines: {node: '>=18'} peerDependencies: zod: ^3.25.76 || ^4.1.8 - '@ai-sdk/openai@3.0.48': - resolution: {integrity: sha512-ALmj/53EXpcRqMbGpPJPP4UOSWw0q4VGpnDo7YctvsynjkrKDmoneDG/1a7VQnSPYHnJp6tTRMf5ZdxZ5whulg==} + '@ai-sdk/openai@3.0.49': + resolution: {integrity: sha512-U2f0pCyNn/jQH3wjgxr8o9VvCkuDFTtXbIhbFFtgXqCzMbed6rBnvzQcAMEK0/Pa44byL9zfcvCOFOflvkRA8w==} engines: {node: '>=18'} peerDependencies: zod: ^3.25.76 || ^4.1.8 @@ -452,48 +455,48 @@ packages: '@aws-crypto/util@5.2.0': resolution: {integrity: sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==} - '@aws-sdk/client-s3@3.1019.0': - resolution: {integrity: sha512-0pb9x7PPhS4oEi4c0rL3vzQQoXA4cWKtPuGga/UfVYLZ68yrqdq0NDKg0fr55qzdhNvWFCpmGx73g9Iyy03kkA==} + '@aws-sdk/client-s3@3.1022.0': + resolution: {integrity: sha512-PhdIW0LxjzcMlBiCldRefnyZk84wtYGnEV0sNGOD55DZTvZsibG2XHvQiL1aFliKugfAhuIpNmFkctI2n2I3Dg==} engines: {node: '>=20.0.0'} - '@aws-sdk/core@3.973.25': - resolution: {integrity: sha512-TNrx7eq6nKNOO62HWPqoBqPLXEkW6nLZQGwjL6lq1jZtigWYbK1NbCnT7mKDzbLMHZfuOECUt3n6CzxjUW9HWQ==} + '@aws-sdk/core@3.973.26': + resolution: {integrity: sha512-A/E6n2W42ruU+sfWk+mMUOyVXbsSgGrY3MJ9/0Az5qUdG67y8I6HYzzoAa+e/lzxxl1uCYmEL6BTMi9ZiZnplQ==} engines: {node: '>=20.0.0'} '@aws-sdk/crc64-nvme@3.972.5': resolution: {integrity: sha512-2VbTstbjKdT+yKi8m7b3a9CiVac+pL/IY2PHJwsaGkkHmuuqkJZIErPck1h6P3T9ghQMLSdMPyW6Qp7Di5swFg==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-env@3.972.23': - resolution: {integrity: sha512-EamaclJcCEaPHp6wiVknNMM2RlsPMjAHSsYSFLNENBM8Wz92QPc6cOn3dif6vPDQt0Oo4IEghDy3NMDCzY/IvA==} + '@aws-sdk/credential-provider-env@3.972.24': + resolution: {integrity: sha512-FWg8uFmT6vQM7VuzELzwVo5bzExGaKHdubn0StjgrcU5FvuLExUe+k06kn/40uKv59rYzhez8eFNM4yYE/Yb/w==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-http@3.972.25': - resolution: {integrity: sha512-qPymamdPcLp6ugoVocG1y5r69ScNiRzb0hogX25/ij+Wz7c7WnsgjLTaz7+eB5BfRxeyUwuw5hgULMuwOGOpcw==} + '@aws-sdk/credential-provider-http@3.972.26': + resolution: {integrity: sha512-CY4ppZ+qHYqcXqBVi//sdHST1QK3KzOEiLtpLsc9W2k2vfZPKExGaQIsOwcyvjpjUEolotitmd3mUNY56IwDEA==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-ini@3.972.26': - resolution: {integrity: sha512-xKxEAMuP6GYx2y5GET+d3aGEroax3AgGfwBE65EQAUe090lzyJ/RzxPX9s8v7Z6qAk0XwfQl+LrmH05X7YvTeg==} + '@aws-sdk/credential-provider-ini@3.972.28': + resolution: {integrity: sha512-wXYvq3+uQcZV7k+bE4yDXCTBdzWTU9x/nMiKBfzInmv6yYK1veMK0AKvRfRBd72nGWYKcL6AxwiPg9z/pYlgpw==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-login@3.972.26': - resolution: {integrity: sha512-EFcM8RM3TUxnZOfMJo++3PnyxFu1fL/huzmn3Vh+8IWRgqZawUD3cRwwOr+/4bE9DpyHaLOWFAjY0lfK5X9ZkQ==} + '@aws-sdk/credential-provider-login@3.972.28': + resolution: {integrity: sha512-ZSTfO6jqUTCysbdBPtEX5OUR//3rbD0lN7jO3sQeS2Gjr/Y+DT6SbIJ0oT2cemNw3UzKu97sNONd1CwNMthuZQ==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-node@3.972.27': - resolution: {integrity: sha512-jXpxSolfFnPVj6GCTtx3xIdWNoDR7hYC/0SbetGZxOC9UnNmipHeX1k6spVstf7eWJrMhXNQEgXC0pD1r5tXIg==} + '@aws-sdk/credential-provider-node@3.972.29': + resolution: {integrity: sha512-clSzDcvndpFJAggLDnDb36sPdlZYyEs5Zm6zgZjjUhwsJgSWiWKwFIXUVBcbruidNyBdbpOv2tNDL9sX8y3/0g==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-process@3.972.23': - resolution: {integrity: sha512-IL/TFW59++b7MpHserjUblGrdP5UXy5Ekqqx1XQkERXBFJcZr74I7VaSrQT5dxdRMU16xGK4L0RQ5fQG1pMgnA==} + '@aws-sdk/credential-provider-process@3.972.24': + resolution: {integrity: sha512-Q2k/XLrFXhEztPHqj4SLCNID3hEPdlhh1CDLBpNnM+1L8fq7P+yON9/9M1IGN/dA5W45v44ylERfXtDAlmMNmw==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-sso@3.972.26': - resolution: {integrity: sha512-c6ghvRb6gTlMznWhGxn/bpVCcp0HRaz4DobGVD9kI4vwHq186nU2xN/S7QGkm0lo0H2jQU8+dgpUFLxfTcwCOg==} + '@aws-sdk/credential-provider-sso@3.972.28': + resolution: {integrity: sha512-IoUlmKMLEITFn1SiCTjPfR6KrE799FBo5baWyk/5Ppar2yXZoUdaRqZzJzK6TcJxx450M8m8DbpddRVYlp5R/A==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-web-identity@3.972.26': - resolution: {integrity: sha512-cXcS3+XD3iwhoXkM44AmxjmbcKueoLCINr1e+IceMmCySda5ysNIfiGBGe9qn5EMiQ9Jd7pP0AGFtcd6OV3Lvg==} + '@aws-sdk/credential-provider-web-identity@3.972.28': + resolution: {integrity: sha512-d+6h0SD8GGERzKe27v5rOzNGKOl0D+l0bWJdqrxH8WSQzHzjsQFIAPgIeOTUwBHVsKKwtSxc91K/SWax6XgswQ==} engines: {node: '>=20.0.0'} '@aws-sdk/middleware-bucket-endpoint@3.972.8': @@ -504,8 +507,8 @@ packages: resolution: {integrity: sha512-5DTBTiotEES1e2jOHAq//zyzCjeMB78lEHd35u15qnrid4Nxm7diqIf9fQQ3Ov0ChH1V3Vvt13thOnrACmfGVQ==} engines: {node: '>=20.0.0'} - '@aws-sdk/middleware-flexible-checksums@3.974.5': - resolution: {integrity: sha512-SPSvF0G1t8m8CcB0L+ClNFszzQOvXaxmRj25oRWDf6aU+TuN2PXPFAJ9A6lt1IvX4oGAqqbTdMPTYs/SSHUYYQ==} + '@aws-sdk/middleware-flexible-checksums@3.974.6': + resolution: {integrity: sha512-YckB8k1ejbyCg/g36gUMFLNzE4W5cERIa4MtsdO+wpTmJEP0+TB7okWIt7d8TDOvnb7SwvxJ21E4TGOBxFpSWQ==} engines: {node: '>=20.0.0'} '@aws-sdk/middleware-host-header@3.972.8': @@ -524,32 +527,32 @@ packages: resolution: {integrity: sha512-/Wt5+CT8dpTFQxEJ9iGy/UGrXr7p2wlIOEHvIr/YcHYByzoLjrqkYqXdJjd9UIgWjv7eqV2HnFJen93UTuwfTQ==} engines: {node: '>=20.0.0'} - '@aws-sdk/middleware-sdk-s3@3.972.26': - resolution: {integrity: sha512-5q7UGSTtt7/KF0Os8wj2VZtlLxeWJVb0e2eDrDJlWot2EIxUNKDDMPFq/FowUqrwZ40rO2bu6BypxaKNvQhI+g==} + '@aws-sdk/middleware-sdk-s3@3.972.27': + resolution: {integrity: sha512-gomO6DZwx+1D/9mbCpcqO5tPBqYBK7DtdgjTIjZ4yvfh/S7ETwAPS0XbJgP2JD8Ycr5CwVrEkV1sFtu3ShXeOw==} engines: {node: '>=20.0.0'} '@aws-sdk/middleware-ssec@3.972.8': resolution: {integrity: sha512-wqlK0yO/TxEC2UsY9wIlqeeutF6jjLe0f96Pbm40XscTo57nImUk9lBcw0dPgsm0sppFtAkSlDrfpK+pC30Wqw==} engines: {node: '>=20.0.0'} - '@aws-sdk/middleware-user-agent@3.972.26': - resolution: {integrity: sha512-AilFIh4rI/2hKyyGN6XrB0yN96W2o7e7wyrPWCM6QjZM1mcC/pVkW3IWWRvuBWMpVP8Fg+rMpbzeLQ6dTM4gig==} + '@aws-sdk/middleware-user-agent@3.972.28': + resolution: {integrity: sha512-cfWZFlVh7Va9lRay4PN2A9ARFzaBYcA097InT5M2CdRS05ECF5yaz86jET8Wsl2WcyKYEvVr/QNmKtYtafUHtQ==} engines: {node: '>=20.0.0'} - '@aws-sdk/nested-clients@3.996.16': - resolution: {integrity: sha512-L7Qzoj/qQU1cL5GnYLQP5LbI+wlLCLoINvcykR3htKcQ4tzrPf2DOs72x933BM7oArYj1SKrkb2lGlsJHIic3g==} + '@aws-sdk/nested-clients@3.996.18': + resolution: {integrity: sha512-c7ZSIXrESxHKx2Mcopgd8AlzZgoXMr20fkx5ViPWPOLBvmyhw9VwJx/Govg8Ef/IhEon5R9l53Z8fdYSEmp6VA==} engines: {node: '>=20.0.0'} '@aws-sdk/region-config-resolver@3.972.10': resolution: {integrity: sha512-1dq9ToC6e070QvnVhhbAs3bb5r6cQ10gTVc6cyRV5uvQe7P138TV2uG2i6+Yok4bAkVAcx5AqkTEBUvWEtBlsQ==} engines: {node: '>=20.0.0'} - '@aws-sdk/signature-v4-multi-region@3.996.14': - resolution: {integrity: sha512-4nZSrBr1NO+48HCM/6BRU8mnRjuHZjcpziCvLXZk5QVftwWz5Mxqbhwdz4xf7WW88buaTB8uRO2MHklSX1m0vg==} + '@aws-sdk/signature-v4-multi-region@3.996.15': + resolution: {integrity: sha512-Ukw2RpqvaL96CjfH/FgfBmy/ZosHBqoHBCFsN61qGg99F33vpntIVii8aNeh65XuOja73arSduskoa4OJea9RQ==} engines: {node: '>=20.0.0'} - '@aws-sdk/token-providers@3.1019.0': - resolution: {integrity: sha512-OF+2RfRmUKyjzrRWlDcyju3RBsuqcrYDQ8TwrJg8efcOotMzuZN4U9mpVTIdATpmEc4lWNZBMSjPzrGm6JPnAQ==} + '@aws-sdk/token-providers@3.1021.0': + resolution: {integrity: sha512-TKY6h9spUk3OLs5v1oAgW9mAeBE3LAGNBwJokLy96wwmd4W2v/tYlXseProyed9ValDj2u1jK/4Rg1T+1NXyJA==} engines: {node: '>=20.0.0'} '@aws-sdk/types@3.973.6': @@ -571,8 +574,8 @@ packages: '@aws-sdk/util-user-agent-browser@3.972.8': resolution: {integrity: sha512-B3KGXJviV2u6Cdw2SDY2aDhoJkVfY/Q/Trwk2CMSkikE1Oi6gRzxhvhIfiRpHfmIsAhV4EA54TVEX8K6CbHbkA==} - '@aws-sdk/util-user-agent-node@3.973.12': - resolution: {integrity: sha512-8phW0TS8ntENJgDcFewYT/Q8dOmarpvSxEjATu2GUBAutiHr++oEGCiBUwxslCMNvwW2cAPZNT53S/ym8zm/gg==} + '@aws-sdk/util-user-agent-node@3.973.14': + resolution: {integrity: sha512-vNSB/DYaPOyujVZBg/zUznH9QC142MaTHVmaFlF7uzzfg3CgT9f/l4C0Yi+vU/tbBhxVcXVB90Oohk5+o+ZbWw==} engines: {node: '>=20.0.0'} peerDependencies: aws-crt: '>=1.0.0' @@ -1203,6 +1206,10 @@ packages: '@types/react': optional: true + '@bcoe/v8-coverage@1.0.2': + resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} + engines: {node: '>=18'} + '@better-auth/api-key@1.5.6': resolution: {integrity: sha512-jr3m4/caFxn9BuY9pGDJ4B1HP1Qoqmyd7heBHm4KUFel+a9Whe/euROgZ/L+o7mbmUdZtreneaU15dpn0tJZ5g==} peerDependencies: @@ -2074,8 +2081,8 @@ packages: '@messageformat/parser@5.1.1': resolution: {integrity: sha512-3p0YRGCcTUCYvBKLIxtDDyrJ0YijGIwrTRu1DT8gIviIDZru8H23+FkY6MJBzM1n9n20CiM4VeDYuBsrrwnLjg==} - '@modelcontextprotocol/sdk@1.28.0': - resolution: {integrity: sha512-gmloF+i+flI8ouQK7MWW4mOwuMh4RePBuPFAEPC6+pdqyWOUMDOixb6qZ69owLJpz6XmyllCouc4t8YWO+E2Nw==} + '@modelcontextprotocol/sdk@1.29.0': + resolution: {integrity: sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==} engines: {node: '>=18'} peerDependencies: '@cfworker/json-schema': ^4.1.1 @@ -3227,10 +3234,6 @@ packages: resolution: {integrity: sha512-gVaaGtKYMYAMmI8buULVH3A2TXVJ98QiwGwI7ddrWGuGidGC2uRt4FHs22+8iROJ0QTzju9CuMjlVsrvpqsdhA==} engines: {node: '>=20'} - '@smithy/abort-controller@4.2.12': - resolution: {integrity: sha512-xolrFw6b+2iYGl6EcOL7IJY71vvyZ0DJ3mcKtpykqPe2uscwtzDZJa1uVQXyP7w9Dd+kGwYnPbMsJrGISKiY/Q==} - engines: {node: '>=18.0.0'} - '@smithy/chunked-blob-reader-native@4.2.3': resolution: {integrity: sha512-jA5k5Udn7Y5717L86h4EIv06wIr3xn8GM1qHRi/Nf31annXcXHJjBKvgztnbn2TxH3xWrPBfgwHsOwZf0UmQWw==} engines: {node: '>=18.0.0'} @@ -3243,8 +3246,8 @@ packages: resolution: {integrity: sha512-iIzMC5NmOUP6WL6o8iPBjFhUhBZ9pPjpUpQYWMUFQqKyXXzOftbfK8zcQCz/jFV1Psmf05BK5ypx4K2r4Tnwdg==} engines: {node: '>=18.0.0'} - '@smithy/core@3.23.12': - resolution: {integrity: sha512-o9VycsYNtgC+Dy3I0yrwCqv9CWicDnke0L7EVOrZtJpjb2t0EjaEofmMrYc0T1Kn3yk32zm6cspxF9u9Bj7e5w==} + '@smithy/core@3.23.13': + resolution: {integrity: sha512-J+2TT9D6oGsUVXVEMvz8h2EmdVnkBiy2auCie4aSJMvKlzUtO5hqjEzXhoCUkIMo7gAYjbQcN0g/MMSXEhDs1Q==} engines: {node: '>=18.0.0'} '@smithy/credential-provider-imds@4.2.12': @@ -3307,16 +3310,16 @@ packages: resolution: {integrity: sha512-YE58Yz+cvFInWI/wOTrB+DbvUVz/pLn5mC5MvOV4fdRUc6qGwygyngcucRQjAhiCEbmfLOXX0gntSIcgMvAjmA==} engines: {node: '>=18.0.0'} - '@smithy/middleware-endpoint@4.4.27': - resolution: {integrity: sha512-T3TFfUgXQlpcg+UdzcAISdZpj4Z+XECZ/cefgA6wLBd6V4lRi0svN2hBouN/be9dXQ31X4sLWz3fAQDf+nt6BA==} + '@smithy/middleware-endpoint@4.4.28': + resolution: {integrity: sha512-p1gfYpi91CHcs5cBq982UlGlDrxoYUX6XdHSo91cQ2KFuz6QloHosO7Jc60pJiVmkWrKOV8kFYlGFFbQ2WUKKQ==} engines: {node: '>=18.0.0'} - '@smithy/middleware-retry@4.4.44': - resolution: {integrity: sha512-Y1Rav7m5CFRPQyM4CI0koD/bXjyjJu3EQxZZhtLGD88WIrBrQ7kqXM96ncd6rYnojwOo/u9MXu57JrEvu/nLrA==} + '@smithy/middleware-retry@4.4.46': + resolution: {integrity: sha512-SpvWNNOPOrKQGUqZbEPO+es+FRXMWvIyzUKUOYdDgdlA6BdZj/R58p4umoQ76c2oJC44PiM7mKizyyex1IJzow==} engines: {node: '>=18.0.0'} - '@smithy/middleware-serde@4.2.15': - resolution: {integrity: sha512-ExYhcltZSli0pgAKOpQQe1DLFBLryeZ22605y/YS+mQpdNWekum9Ujb/jMKfJKgjtz1AZldtwA/wCYuKJgjjlg==} + '@smithy/middleware-serde@4.2.16': + resolution: {integrity: sha512-beqfV+RZ9RSv+sQqor3xroUUYgRFCGRw6niGstPG8zO9LgTl0B0MCucxjmrH/2WwksQN7UUgI7KNANoZv+KALA==} engines: {node: '>=18.0.0'} '@smithy/middleware-stack@4.2.12': @@ -3327,8 +3330,8 @@ packages: resolution: {integrity: sha512-tr2oKX2xMcO+rBOjobSwVAkV05SIfUKz8iI53rzxEmgW3GOOPOv0UioSDk+J8OpRQnpnhsO3Af6IEBabQBVmiw==} engines: {node: '>=18.0.0'} - '@smithy/node-http-handler@4.5.0': - resolution: {integrity: sha512-Rnq9vQWiR1+/I6NZZMNzJHV6pZYyEHt2ZnuV3MG8z2NNenC4i/8Kzttz7CjZiHSmsN5frhXhg17z3Zqjjhmz1A==} + '@smithy/node-http-handler@4.5.1': + resolution: {integrity: sha512-ejjxdAXjkPIs9lyYyVutOGNOraqUE9v/NjGMKwwFrfOM354wfSD8lmlj8hVwUzQmlLLF4+udhfCX9Exnbmvfzw==} engines: {node: '>=18.0.0'} '@smithy/property-provider@4.2.12': @@ -3359,8 +3362,8 @@ packages: resolution: {integrity: sha512-B/FBwO3MVOL00DaRSXfXfa/TRXRheagt/q5A2NM13u7q+sHS59EOVGQNfG7DkmVtdQm5m3vOosoKAXSqn/OEgw==} engines: {node: '>=18.0.0'} - '@smithy/smithy-client@4.12.7': - resolution: {integrity: sha512-q3gqnwml60G44FECaEEsdQMplYhDMZYCtYhMCzadCnRnnHIobZJjegmdoUo6ieLQlPUzvrMdIJUpx6DoPmzANQ==} + '@smithy/smithy-client@4.12.8': + resolution: {integrity: sha512-aJaAX7vHe5i66smoSSID7t4rKY08PbD8EBU7DOloixvhOozfYWdcSYE4l6/tjkZ0vBZhGjheWzB2mh31sLgCMA==} engines: {node: '>=18.0.0'} '@smithy/types@4.13.1': @@ -3395,12 +3398,12 @@ packages: resolution: {integrity: sha512-dWU03V3XUprJwaUIFVv4iOnS1FC9HnMHDfUrlNDSh4315v0cWyaIErP8KiqGVbf5z+JupoVpNM7ZB3jFiTejvQ==} engines: {node: '>=18.0.0'} - '@smithy/util-defaults-mode-browser@4.3.43': - resolution: {integrity: sha512-Qd/0wCKMaXxev/z00TvNzGCH2jlKKKxXP1aDxB6oKwSQthe3Og2dMhSayGCnsma1bK/kQX1+X7SMP99t6FgiiQ==} + '@smithy/util-defaults-mode-browser@4.3.44': + resolution: {integrity: sha512-eZg6XzaCbVr2S5cAErU5eGBDaOVTuTo1I65i4tQcHENRcZ8rMWhQy1DaIYUSLyZjsfXvmCqZrstSMYyGFocvHA==} engines: {node: '>=18.0.0'} - '@smithy/util-defaults-mode-node@4.2.47': - resolution: {integrity: sha512-qSRbYp1EQ7th+sPFuVcVO05AE0QH635hycdEXlpzIahqHHf2Fyd/Zl+8v0XYMJ3cgDVPa0lkMefU7oNUjAP+DQ==} + '@smithy/util-defaults-mode-node@4.2.48': + resolution: {integrity: sha512-FqOKTlqSaoV3nzO55pMs5NBnZX8EhoI0DGmn9kbYeXWppgHD6dchyuj2HLqp4INJDJbSrj6OFYJkAh/WhSzZPg==} engines: {node: '>=18.0.0'} '@smithy/util-endpoints@3.3.3': @@ -3415,12 +3418,12 @@ packages: resolution: {integrity: sha512-Er805uFUOvgc0l8nv0e0su0VFISoxhJ/AwOn3gL2NWNY2LUEldP5WtVcRYSQBcjg0y9NfG8JYrCJaYDpupBHJQ==} engines: {node: '>=18.0.0'} - '@smithy/util-retry@4.2.12': - resolution: {integrity: sha512-1zopLDUEOwumjcHdJ1mwBHddubYF8GMQvstVCLC54Y46rqoHwlIU+8ZzUeaBcD+WCJHyDGSeZ2ml9YSe9aqcoQ==} + '@smithy/util-retry@4.2.13': + resolution: {integrity: sha512-qQQsIvL0MGIbUjeSrg0/VlQ3jGNKyM3/2iU3FPNgy01z+Sp4OvcaxbgIoFOTvB61ZoohtutuOvOcgmhbD0katQ==} engines: {node: '>=18.0.0'} - '@smithy/util-stream@4.5.20': - resolution: {integrity: sha512-4yXLm5n/B5SRBR2p8cZ90Sbv4zL4NKsgxdzCzp/83cXw2KxLEumt5p+GAVyRNZgQOSrzXn9ARpO0lUe8XSlSDw==} + '@smithy/util-stream@4.5.21': + resolution: {integrity: sha512-KzSg+7KKywLnkoKejRtIBXDmwBfjGvg1U1i/etkC7XSWUyFCoLno1IohV2c74IzQqdhX5y3uE44r/8/wuK+A7Q==} engines: {node: '>=18.0.0'} '@smithy/util-uri-escape@4.2.2': @@ -3435,8 +3438,8 @@ packages: resolution: {integrity: sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==} engines: {node: '>=18.0.0'} - '@smithy/util-waiter@4.2.13': - resolution: {integrity: sha512-2zdZ9DTHngRtcYxJK1GUDxruNr53kv5W2Lupe0LMU+Imr6ohQg8M2T14MNkj1Y0wS3FFwpgpGQyvuaMF7CiTmQ==} + '@smithy/util-waiter@4.2.14': + resolution: {integrity: sha512-2zqq5o/oizvMaFUlNiTyZ7dbgYv1a893aGut2uaxtbzTx/VYYnRxWzDHuD/ftgcw94ffenua+ZNLrbqwUYE+Bg==} engines: {node: '>=18.0.0'} '@smithy/uuid@1.1.2': @@ -3567,11 +3570,11 @@ packages: resolution: {integrity: sha512-NaOGLRrddszbQj9upGat6HG/4TKvXLvu+osAIgfxPYA+eIvYKv8GKDJOrY2D3/U9MRnKfMWD7bU4jeD4xmqyIg==} engines: {node: '>=20.19'} - '@tanstack/query-core@5.95.2': - resolution: {integrity: sha512-o4T8vZHZET4Bib3jZ/tCW9/7080urD4c+0/AUaYVpIqOsr7y0reBc1oX3ttNaSW5mYyvZHctiQ/UOP2PfdmFEQ==} + '@tanstack/query-core@5.96.1': + resolution: {integrity: sha512-u1yBgtavSy+N8wgtW3PiER6UpxcplMje65yXnnVgiHTqiMwLlxiw4WvQDrXyn+UD6lnn8kHaxmerJUzQcV/MMg==} - '@tanstack/react-query@5.95.2': - resolution: {integrity: sha512-/wGkvLj/st5Ud1Q76KF1uFxScV7WeqN1slQx5280ycwAyYkIPGaRZAEgHxe3bjirSd5Zpwkj6zNcR4cqYni/ZA==} + '@tanstack/react-query@5.96.1': + resolution: {integrity: sha512-2X7KYK5KKWUKGeWCVcqxXAkYefJtrKB7tSKWgeG++b0H6BRHxQaLSSi8AxcgjmUnnosHuh9WsFZqvE16P1WCzA==} peerDependencies: react: ^18 || ^19 @@ -3585,29 +3588,29 @@ packages: react: '>=18.0.0 || >=19.0.0' react-dom: '>=18.0.0 || >=19.0.0' - '@tanstack/react-router@1.168.8': - resolution: {integrity: sha512-t0S0QueXubBKmI9eLPcN/A1sLQgTu8/yHerjrvvsGeD12zMdw0uJPKwEKpStQF2OThQtw64cs34uUSYXBUTSNw==} + '@tanstack/react-router@1.168.10': + resolution: {integrity: sha512-/RmDlOwDkCug609KdPB3U+U1zmrtadJpvsmRg2zEn8TRCKRNri7dYZIjQZbNg8PgUiRL4T6njrZBV1ChzblNaA==} engines: {node: '>=20.19'} peerDependencies: react: '>=18.0.0 || >=19.0.0' react-dom: '>=18.0.0 || >=19.0.0' - '@tanstack/react-start-client@1.166.23': - resolution: {integrity: sha512-lNIdRuK6hIeUfDQEO9pLnFAnQ1/Q8tpMt297lixsci2u+g+EcTh0CwEFzkZRDq7py8Jiv53/cx1orq+RAMjUMg==} + '@tanstack/react-start-client@1.166.25': + resolution: {integrity: sha512-FvD279zzneUtsfhaTv2c29qhE1Z3wHy3dt3cCjn9LzWZehOgn5Ij78s0YpmQaQ8lSF3YL7CySE3pDk9XHE6YeA==} engines: {node: '>=22.12.0'} peerDependencies: react: '>=18.0.0 || >=19.0.0' react-dom: '>=18.0.0 || >=19.0.0' - '@tanstack/react-start-server@1.166.23': - resolution: {integrity: sha512-+ijM4apEyZSbPa5d27uxbaNWQP1xYnyVbCZg9ZbognhQob797e023lCK0yxaGYAGPltT7w/FC4msjTfFW+afNg==} + '@tanstack/react-start-server@1.166.25': + resolution: {integrity: sha512-bPLADxlplvcnAcnZvBjJl2MzgUnB85d7Mu5aEkYoOFxhz0WiG6mZp7BDadIJuCd33NYMirsd3XrjfCHNzrMTyg==} engines: {node: '>=22.12.0'} peerDependencies: react: '>=18.0.0 || >=19.0.0' react-dom: '>=18.0.0 || >=19.0.0' - '@tanstack/react-start@1.167.13': - resolution: {integrity: sha512-AM1mrQxoEu4/cWGmTO0RpWpZuryiPiMn75Il0fnYin4+PCKtqpvT6M5BjB5MEdoQ8NIhYdMZaC4D9IVEC0kESw==} + '@tanstack/react-start@1.167.16': + resolution: {integrity: sha512-vHIhn+FTWfAVhRus1BZEaBZPhnYL+StDuMlShslIBPEGGTCRt11BxNUfV/iDpr7zbxw36Snj7zGfI7DwfjjlDQ==} engines: {node: '>=22.12.0'} hasBin: true peerDependencies: @@ -3621,22 +3624,22 @@ packages: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - '@tanstack/router-core@1.168.7': - resolution: {integrity: sha512-z4UEdlzMrFaKBsG4OIxlZEm+wsYBtEp//fnX6kW18jhQpETNcM6u2SXNdX+bcIYp6AaR7ERS3SBENzjC/xxwQQ==} + '@tanstack/router-core@1.168.9': + resolution: {integrity: sha512-18oeEwEDyXOIuO1VBP9ACaK7tYHZUjynGDCoUh/5c/BNhia9vCJCp9O0LfhZXOorDc/PmLSgvmweFhVmIxF10g==} engines: {node: '>=20.19'} hasBin: true - '@tanstack/router-generator@1.166.22': - resolution: {integrity: sha512-wQ7H8/Q2rmSPuaxWnurJ3DATNnqWV2tajxri9TSiW4QHsG7cWPD34+goeIinKG+GajJyEdfVpz6w/gRJXfbAPw==} + '@tanstack/router-generator@1.166.24': + resolution: {integrity: sha512-vdaGKwuH+r+DPe6R1mjk+TDDmDH6NTG7QqwxHqGEvOH4aGf9sPjhmRKNJZqQr8cPIbfp6u5lXyZ1TeDcSNMVEA==} engines: {node: '>=20.19'} - '@tanstack/router-plugin@1.167.9': - resolution: {integrity: sha512-h/VV05FEHd4PVyc5Zy8B3trWLcdLt/Pmp+mfifmBKGRw+MUtvdQKbBHhmy4ouOf67s5zDJMc+n8R3xgU7bDwFA==} + '@tanstack/router-plugin@1.167.12': + resolution: {integrity: sha512-StEHcctCuFI5taSjO+lhR/yQ+EK63BdyYa+ne6FoNQPB3MMrOUrz2ZVnbqILRLkh2b+p2EfBKt65sgAKdKygPQ==} engines: {node: '>=20.19'} hasBin: true peerDependencies: '@rsbuild/core': '>=1.0.2' - '@tanstack/react-router': ^1.168.8 + '@tanstack/react-router': ^1.168.10 vite: '>=5.0.0 || >=6.0.0 || >=7.0.0' vite-plugin-solid: ^2.11.10 webpack: '>=5.92.0' @@ -3663,8 +3666,8 @@ packages: resolution: {integrity: sha512-nRcYw+w2OEgK6VfjirYvGyPLOK+tZQz1jkYcmH5AjMamQ9PycnlxZF2aEZtPpNoUsaceX2bHptn6Ub5hGXqNvw==} engines: {node: '>=20.19'} - '@tanstack/start-client-core@1.167.7': - resolution: {integrity: sha512-WKQYbaSKZzKdi7kaZHtIVlb2EFZin+EFNoE3sl53rcJjQNEcrmUHjLMBjxYQejD13dmIeQtPpKvPAM2s3uJ42g==} + '@tanstack/start-client-core@1.167.9': + resolution: {integrity: sha512-2ETQO/bxiZGsoTdPxZb7xR8YqCy5l4kv/QPkwIXuvx/A4BjufngXfgISjXUicXsFRIBZeiFnBzp9A38UMsS2iA==} engines: {node: '>=22.12.0'} hasBin: true @@ -3672,19 +3675,19 @@ packages: resolution: {integrity: sha512-Y6QSlGiLga8cHfvxGGaonXIlt2bIUTVdH6AMjmpMp7+ANNCp+N96GQbjjhLye3JkaxDfP68x5iZA8NK4imgRig==} engines: {node: '>=22.12.0'} - '@tanstack/start-plugin-core@1.167.14': - resolution: {integrity: sha512-jSAyrmhMNe1b0UrBD/IB3ttLdmt5//jylOujxa39mUuyDzQGXITC5kacm26j/uJ713qfc3u17UtVKyo47UHmKA==} + '@tanstack/start-plugin-core@1.167.17': + resolution: {integrity: sha512-OkorpOobGOEDVr72QUmkzKjbawKC05CSz+1B3OObB/AxBIIw+lLLhTXbV45QkX2LZA7dcRvPJYZGOH1pkFqA1g==} engines: {node: '>=22.12.0'} peerDependencies: vite: '>=7.0.0' - '@tanstack/start-server-core@1.167.7': - resolution: {integrity: sha512-P60/B0bj5bYFRGbHzHu/3RU0uIpoUwV+9f06lBUccaONyPuDbcP54d/vB6kJ109yvgREocilU5E83aOEnNPUkg==} + '@tanstack/start-server-core@1.167.9': + resolution: {integrity: sha512-vKkslQIihoDDVumF73VXT7PVFmN7Nea0nKhZx7gMbc0m09yPQYYR1dn86/dz14k6/7cDkJ+qKXa09rlVlN/i9Q==} engines: {node: '>=22.12.0'} hasBin: true - '@tanstack/start-storage-context@1.166.21': - resolution: {integrity: sha512-//vNgC2WK5Dw/j01i9AWq3VW7k8dqudDH9O86LFPmygfoKAhVONYs0CgWReoLVpjcc5QsMrjLI2Y95DF656acQ==} + '@tanstack/start-storage-context@1.166.23': + resolution: {integrity: sha512-3vEdiYRMx+r+Q7Xqxj3YmADPIpMm7fkKxDa8ITwodGXiw+SBJCGkpBXGUWjOXyXkIyqGHKM5UrReTcVUTkmaug==} engines: {node: '>=22.12.0'} '@tanstack/store@0.9.3': @@ -3705,170 +3708,189 @@ packages: '@tediousjs/connection-string@0.5.0': resolution: {integrity: sha512-7qSgZbincDDDFyRweCIEvZULFAw5iz/DeunhvuxpL31nfntX3P4Yd4HkHBRg9H8CdqY1e5WFN1PZIz/REL9MVQ==} - '@tiptap/core@3.21.0': - resolution: {integrity: sha512-IfnQiuEeabDSPr1C/zHFTbnvlTf5z0DE/d/xz4C6bkL4ZBDJ3rr99h2qsaV0l8F+kbNswZMlQdM8rxNlMy95fQ==} - peerDependencies: - '@tiptap/pm': ^3.21.0 + '@testing-library/dom@10.4.1': + resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} + engines: {node: '>=18'} - '@tiptap/extension-blockquote@3.21.0': - resolution: {integrity: sha512-JDM/RR6rM0dMCZ1UnEf7eqmN6pAdIa2llhN+E24HdTGNJCklMFhLAGE/OT8/1r7M0WWA9GVO7/PTe4EdGh6+lQ==} + '@testing-library/react@16.3.2': + resolution: {integrity: sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==} + engines: {node: '>=18'} peerDependencies: - '@tiptap/core': ^3.21.0 + '@testing-library/dom': ^10.0.0 + '@types/react': ^18.0.0 || ^19.0.0 + '@types/react-dom': ^18.0.0 || ^19.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - '@tiptap/extension-bold@3.21.0': - resolution: {integrity: sha512-iyEJRzG7XTCPlHwEDzUw3HnuYYCfL7lNpcCHmxcpYMrIUA8rv7EUxerIwApT6xY8hQ/07ljuJKgOyPvnJOOzuA==} + '@tiptap/core@3.22.0': + resolution: {integrity: sha512-EA/XFbvvz0yRyccqrgOwB9RQe6+uJ8NszjLKH9+3xPE2/+Sa2imax0IqWl7YOXkWihdQVrlpP+EpQF9APKx3jg==} peerDependencies: - '@tiptap/core': ^3.21.0 + '@tiptap/pm': ^3.22.0 - '@tiptap/extension-bubble-menu@3.21.0': - resolution: {integrity: sha512-/fabRRhhf8i4LAx9e8xz9ppqN5KgdJk3TxMuxAD5vAWGsejvhSoPa8O8H/QwwyntXm1Vue8aQiMHsUk48b2hGQ==} + '@tiptap/extension-blockquote@3.22.0': + resolution: {integrity: sha512-WF7K1jtEhkhCZFOoei3QrUHMsM6i9eqXw1IuL6cAX3+CBpqVg89KbP/cJp05dYKU0SO0LJkn87biKVqcnAcN7A==} peerDependencies: - '@tiptap/core': ^3.21.0 - '@tiptap/pm': ^3.21.0 + '@tiptap/core': ^3.22.0 - '@tiptap/extension-bullet-list@3.21.0': - resolution: {integrity: sha512-PWNF+xwxgOeXYGD88sCQLKL0eBoQqjUnZNALxBjN3Y7x4llalh42rHOp2Nt2t6UbQgqTBtBzU/uFcussTpxreQ==} + '@tiptap/extension-bold@3.22.0': + resolution: {integrity: sha512-mPG1FzOy2DVaJHHuX/eQPIuYie0kqG07M04nElBY8QlV0oYB4/kd0Aubz+m9czqHx/F9u/L98kmMFhCh2DWk2w==} peerDependencies: - '@tiptap/extension-list': ^3.21.0 + '@tiptap/core': ^3.22.0 - '@tiptap/extension-code-block@3.21.0': - resolution: {integrity: sha512-zrVOcOzDCjHQ8NJcC+qHmZZKiwnP/NMSb3qVJlSMN8TzuHept1MZCDa2Mbo70O6I0txo456SGuXB9sqV1vHmGg==} + '@tiptap/extension-bubble-menu@3.22.0': + resolution: {integrity: sha512-792CUdP0roO17jQJ+fflSJEWfw2cAric61nV2291a2iL7L/6mNJGP4QFim1FqZzfx3/FwHSXEI3NYT6wdUlh8w==} peerDependencies: - '@tiptap/core': ^3.21.0 - '@tiptap/pm': ^3.21.0 + '@tiptap/core': ^3.22.0 + '@tiptap/pm': ^3.22.0 - '@tiptap/extension-code@3.21.0': - resolution: {integrity: sha512-D7wA9jp+4X2r1f3FIoga73s6Rn4rmZY57Jes6a4rK3HY+3yHk1r057pPIZSY8Drfs97jxHQVFdfUYUomLSFYBA==} + '@tiptap/extension-bullet-list@3.22.0': + resolution: {integrity: sha512-GUBYUXlNMxfJVpbDQnwNU54rqIBHEIQ7KyPptghSzvmnwMlr6n10OmYSGSVvNpOAIYkHkuhzkwgUDeU+YzWbjg==} peerDependencies: - '@tiptap/core': ^3.21.0 + '@tiptap/extension-list': ^3.22.0 - '@tiptap/extension-document@3.21.0': - resolution: {integrity: sha512-7oCyzXI9ChvJQUlr23AURdfVar4OIsrYUvqdhEwo3bjcI/Q/j0KJiXfuh6ZzL5eVaINSailH53sZaGg4THQtUg==} + '@tiptap/extension-code-block@3.22.0': + resolution: {integrity: sha512-HtnYHj6yHVy2dKs02j5dyEehWQMGOGRMiZBkefY3TwSSNzGESVcFfDV+Xr87j7zDGYvY16vOtVmyRTOTqPn49A==} peerDependencies: - '@tiptap/core': ^3.21.0 + '@tiptap/core': ^3.22.0 + '@tiptap/pm': ^3.22.0 - '@tiptap/extension-dropcursor@3.21.0': - resolution: {integrity: sha512-6fsDSVAM2iz7eElvT6iivMrGBGjIP/oPigVZ/SPm6f31phaYhz6TIOEgV/Lr2jaPIOgyK4U0cU4Yd4KUBCmhzQ==} + '@tiptap/extension-code@3.22.0': + resolution: {integrity: sha512-JGxByyyUdR0yRt1mOxnA2dp6PmI9pr6C846UkZtuOCwhBOBLkoBGkZqW4FytLPOfWGVJpm7w5tx7h1n5uNwfag==} peerDependencies: - '@tiptap/extensions': ^3.21.0 + '@tiptap/core': ^3.22.0 - '@tiptap/extension-floating-menu@3.21.0': - resolution: {integrity: sha512-n2HzTB+I/5rAl8R/1sKMv92JiY1oDK1hroXizxEKYa6dskJcAMW0CfYyPcPOZWQQEe7qoeOvQISr2ooLAKW+Mw==} + '@tiptap/extension-document@3.22.0': + resolution: {integrity: sha512-ZdtuBt2KnxIYBtp/VrKiWQ5cLPw1qDKb+sieipBaDWuvhgDNi1kfr//ByEP8xPhcjJfH/C3PCdYYVwIUJwzdqQ==} + peerDependencies: + '@tiptap/core': ^3.22.0 + + '@tiptap/extension-dropcursor@3.22.0': + resolution: {integrity: sha512-yI0aMD4szbNdy/dlglPbZ9Ddc2UucRatJSifmtenCLg7YWyIIYton0T6Uym+FXAEUZ6KsaoNqEKiUbK5cRzZMQ==} + peerDependencies: + '@tiptap/extensions': ^3.22.0 + + '@tiptap/extension-floating-menu@3.22.0': + resolution: {integrity: sha512-6Gg3I6n+YaCJyvpcKheWiOtU9Oy0M3lbwUGdLK7jTxgAG2YOJxEcx2CzDv3PtNcoyAVUVk9Eio21awVMOECLAQ==} peerDependencies: '@floating-ui/dom': ^1.0.0 - '@tiptap/core': ^3.21.0 - '@tiptap/pm': ^3.21.0 + '@tiptap/core': ^3.22.0 + '@tiptap/pm': ^3.22.0 - '@tiptap/extension-gapcursor@3.21.0': - resolution: {integrity: sha512-wGjgAoYBTvPAe9QYMI5px355XcNeMkaUrMY9IHbMqgqdmHcDxqooxM4H6sYVX2CRcHwXy4I8NQAoOhSYrQJDMg==} + '@tiptap/extension-gapcursor@3.22.0': + resolution: {integrity: sha512-VsnaTU88PlA/eG9DtUvuB90z5gVZIaH6T/JVTxGasxR4CFsv0L4Zq5awwr0+SsYH9dKepRMgbanVU03c6k1SuA==} peerDependencies: - '@tiptap/extensions': ^3.21.0 + '@tiptap/extensions': ^3.22.0 - '@tiptap/extension-hard-break@3.21.0': - resolution: {integrity: sha512-6JFVSAOQ1qhQHi9mVcdn2/XO8YIMgYV8zjarzNUzP6Sf2waeE5BLXjlg6rIH/945sY1J+FndTojLru6gQ07a5A==} + '@tiptap/extension-hard-break@3.22.0': + resolution: {integrity: sha512-F51pt3fgjbtWrY0Uud+5HoJW4f7w/aBZvmoCk19nrEY955vvuQQ2PD/DZtecl4A8fF50PpRjgilrYnnh99l0ew==} peerDependencies: - '@tiptap/core': ^3.21.0 + '@tiptap/core': ^3.22.0 - '@tiptap/extension-heading@3.21.0': - resolution: {integrity: sha512-ji6VJmoRnDzAHYflEYEZohMHRi77UGLW1o3ua7UhI32iJ9nuYssbPNuzEeE4SvENMQwZRszad5+a+dKAa+NC7g==} + '@tiptap/extension-heading@3.22.0': + resolution: {integrity: sha512-SnOUBXzh9Dft7HY0rqaSL/kZKg4W9wlHfpnFPW8aIuewXvFDLKa6PisqxPpHsXSbG21kfs5E0MLdwdXtNP89XA==} peerDependencies: - '@tiptap/core': ^3.21.0 + '@tiptap/core': ^3.22.0 - '@tiptap/extension-highlight@3.21.0': - resolution: {integrity: sha512-3f/bVgfm2dJZxalh07TThDxcTaeXJ+dpYyRY9trnFeHbhyYQXSy6yzkNhNcYB4Ua5jxpKQv4b9Q448QVh+KNzA==} + '@tiptap/extension-highlight@3.22.0': + resolution: {integrity: sha512-5+u3Rr3EDF4tmXlMblL1RWwHheO00Gdn974HjUNrKus01q6JQcqBpycXfYYq5XHG+iHdWSVzL2XE1KsTwVI+7w==} peerDependencies: - '@tiptap/core': ^3.21.0 + '@tiptap/core': ^3.22.0 - '@tiptap/extension-horizontal-rule@3.21.0': - resolution: {integrity: sha512-vNBnOfFEY62CoJPGo4nonRM7RiOvhII1vhoO+WFr1GxDqCAfmEFjToflt7JT1UJdo6lMVcD+aaaAgOiuSz5p6g==} + '@tiptap/extension-horizontal-rule@3.22.0': + resolution: {integrity: sha512-9v08PcmJOumVmgGgcuFPZpAk+tf+m7+vaCNsNyf8Ce1i0m3GPSle1ZmxzjDU2FlpaCFrcgoUKlEjKYaFYFCJIg==} peerDependencies: - '@tiptap/core': ^3.21.0 - '@tiptap/pm': ^3.21.0 + '@tiptap/core': ^3.22.0 + '@tiptap/pm': ^3.22.0 - '@tiptap/extension-italic@3.21.0': - resolution: {integrity: sha512-2I8oPvwyXhRn1k8lbDFIutzvhtLEjoO5mmQCNX4TnT4PdxxaSrK9+ihYg12VeqhUeO7dg1MKiFqws0HVBrwzWg==} + '@tiptap/extension-italic@3.22.0': + resolution: {integrity: sha512-+qq9QZF44O1MRqk6w1AMDZ8oDBs5AtdDdNEcdXpzVU54cJAtWyEPEfXtD0B68hOUp/RdZjMdL27fp+4Id7C1YA==} peerDependencies: - '@tiptap/core': ^3.21.0 + '@tiptap/core': ^3.22.0 - '@tiptap/extension-link@3.21.0': - resolution: {integrity: sha512-oMU7Yve1sbgBsaFAUc2R0GPf4d3ZPVJeMUFC6b6X9rJIvx/IhEUEn9toQcSBGfp02uWK9NdQyIFYFdWlVXH++w==} + '@tiptap/extension-link@3.22.0': + resolution: {integrity: sha512-tGMBUAmni532G6R5gnaRvTb6c7+ST1qCHBV0p5kGGzdHaQTDd1R7S8fnuA3M7+6Sruc82iIY+Ur+6Tusvo/vLA==} peerDependencies: - '@tiptap/core': ^3.21.0 - '@tiptap/pm': ^3.21.0 + '@tiptap/core': ^3.22.0 + '@tiptap/pm': ^3.22.0 - '@tiptap/extension-list-item@3.21.0': - resolution: {integrity: sha512-1ZymZmlQVbAoC4q5x3cro0v5+3I6l+BHqbhIMQLjQFlAOJfcE0pvqRzAFW7PduxUj41tXEtsYqp2NREvO9F5Fg==} + '@tiptap/extension-list-item@3.22.0': + resolution: {integrity: sha512-9cFvFLEtf0bnOc/LaGeX2D+c9wOxeqhzgabUl2Ztz8Xzoby4JtamXnrIpb7DG7hZMf3luJMF8bz0HSvAnNiISQ==} peerDependencies: - '@tiptap/extension-list': ^3.21.0 + '@tiptap/extension-list': ^3.22.0 - '@tiptap/extension-list-keymap@3.21.0': - resolution: {integrity: sha512-EzrfW3ASNFPWKhR8sNOq7Kqw4hvaTAOn4dlI7chB8HIANSrlyPOUn+eKAnO6HQgsUgsbjg2GbTUrGrxcoLykUg==} + '@tiptap/extension-list-keymap@3.22.0': + resolution: {integrity: sha512-uCtr5/g+Cwkmsb/VLctgo4VjKm0jv52moAmDyr/TLRjW94gnSLhwXFKzyd7BNIXBQHDyS44UEIJFD3ul4dUKdw==} peerDependencies: - '@tiptap/extension-list': ^3.21.0 + '@tiptap/extension-list': ^3.22.0 - '@tiptap/extension-list@3.21.0': - resolution: {integrity: sha512-KeBlEtLrGce2d3dgL89hmwWEtREuzlW4XY5bYWpKNvCbFqvdSb3n7vkdkw32YclZmMWxAcABgW6ucCStkE0rsQ==} + '@tiptap/extension-list@3.22.0': + resolution: {integrity: sha512-NfSCAgX44NVLib6aN4HmsP1wi6fFfK3dt6TBb9EgcR82nzq6n7dq7VEBw9V1aKqeXQEtNpqMnQFd0SDayweyfQ==} peerDependencies: - '@tiptap/core': ^3.21.0 - '@tiptap/pm': ^3.21.0 + '@tiptap/core': ^3.22.0 + '@tiptap/pm': ^3.22.0 - '@tiptap/extension-ordered-list@3.21.0': - resolution: {integrity: sha512-+d+0orokMfqaBfvr9tUBgGvo2ZCV+fR3JzsJTmnLBWOkhBSJN7H4pnfXPTue0qwspUwRmkLJxdIlU+J7HkMrng==} + '@tiptap/extension-ordered-list@3.22.0': + resolution: {integrity: sha512-B5JSJ2Xe2KPIYYG7jpZHVeAku/VJB+CCgPYl+qIHjZ4JGTnW23qkIA+6dWk6WljGmhQL1qusxZZn4UAnZtBLeQ==} peerDependencies: - '@tiptap/extension-list': ^3.21.0 + '@tiptap/extension-list': ^3.22.0 - '@tiptap/extension-paragraph@3.21.0': - resolution: {integrity: sha512-cMPG/jCoZ9NmLZ5ctFziILaxJGfDtMTb5OLBhifMFZeMVwF1pEJIygDEfnX/HSruv507weZSQG4pERO2tRszMg==} + '@tiptap/extension-paragraph@3.22.0': + resolution: {integrity: sha512-fwkPvbGI3xvzWrTJVGZVocgA99Pgqd5kW7iv7MEWlI9uOUa6Ifu31/seHV7j+QDW3y3mADcx+zyhxcMVELtLjA==} peerDependencies: - '@tiptap/core': ^3.21.0 + '@tiptap/core': ^3.22.0 - '@tiptap/extension-strike@3.21.0': - resolution: {integrity: sha512-easnVaN11Wl+5fOtfvzJ10J762S9TRXZaMj5rLBGavgf82DCYHqhGhBqpLQrJ41r4nPABGlYvTRoxfvBLB74Lg==} + '@tiptap/extension-strike@3.22.0': + resolution: {integrity: sha512-gCgFr1sIcqrJeV5Gdrh8KVZHA+0B1FpFBuOi6FzMyVfBB2sBBqKnjoInYTkPXXdP49Qu8L8hi4luFQtoj4zGzA==} peerDependencies: - '@tiptap/core': ^3.21.0 + '@tiptap/core': ^3.22.0 - '@tiptap/extension-table@3.21.0': - resolution: {integrity: sha512-/iPVM/5TtRy7wGrLW/uL+qjMHsor26P/nH2nHasgPr/EIwliLGhHp9Gwjhix3OTyM2UK+VNcuhH6X50UbcByTQ==} + '@tiptap/extension-table@3.22.0': + resolution: {integrity: sha512-R7vYmlEg91nV51zzajaWj6R9yIDckOvKKeKsmHAhFzrzUjjpia+lJcUBjXHbp2Vnq30QV8EmtBPqpygnr7XGiw==} peerDependencies: - '@tiptap/core': ^3.21.0 - '@tiptap/pm': ^3.21.0 + '@tiptap/core': ^3.22.0 + '@tiptap/pm': ^3.22.0 - '@tiptap/extension-text-align@3.21.0': - resolution: {integrity: sha512-XaoUaq45nai9LLoUStumMItHhhCnmXirPR8mTjEDKZ2QD0Kg/YHEr6guJYc6qKB7YA+Wa1EgBIrZOv1+d2Pdag==} + '@tiptap/extension-text-align@3.22.0': + resolution: {integrity: sha512-wKX5uCCEl31KtBBqXFcKYHMivF9fzME4EwykYZk7qN3HoV+WJMl9D17s5r6TUayN++1G5IiW69UFFiwB9yDugw==} peerDependencies: - '@tiptap/core': ^3.21.0 + '@tiptap/core': ^3.22.0 - '@tiptap/extension-text@3.21.0': - resolution: {integrity: sha512-Zx8QdB8a5iBuE4uO21c3BjmpBfaJEr2Jd1QFnsdgx11fm6P7dGgZaGko1FaINhfOPRGTN6O/kiF02cDMdOHa/w==} + '@tiptap/extension-text@3.22.0': + resolution: {integrity: sha512-FQ3lBRswZbSEbtxOnDF4T7pdsZRmKh/8q+M29zXaDHGfBc6nuGNPlNKSIy0Iryjhf/YmMVaWDpHvzk56KD7QtA==} peerDependencies: - '@tiptap/core': ^3.21.0 + '@tiptap/core': ^3.22.0 - '@tiptap/extension-underline@3.21.0': - resolution: {integrity: sha512-gGmBEymbWnr8AIS8bI/bPw5rcwo7wAFcBw/TsLd1nAanu1dDqSRNDBrit3m02Ru+D88u2SfNvmbOPI1pz+1f5w==} + '@tiptap/extension-underline@3.22.0': + resolution: {integrity: sha512-AxQOnXQwYmZNjagkEoCZZqbpJbLVmBcu1ivJ9dE0SAQsr1wRUp7mAg+g1SqhbMAvrXvv7yhhNevSdQKmXsnFyA==} peerDependencies: - '@tiptap/core': ^3.21.0 + '@tiptap/core': ^3.22.0 - '@tiptap/extensions@3.21.0': - resolution: {integrity: sha512-MN1uh5PmHT1F2BNsbc21MIS0AMFFA73oODlp/4ckpBR4o5AxRwV+8f43Cd52UL4MgMkKj/A+QfZ7iK9IDb0h5A==} + '@tiptap/extensions@3.22.0': + resolution: {integrity: sha512-En8p1FiFBT3V9CduErCyLPFxDRsYLISb2cCtLKTeYVeCRn2vQZK4B8WuOgHI4IBipz3I3XidmDhra4yt8mmi/w==} peerDependencies: - '@tiptap/core': ^3.21.0 - '@tiptap/pm': ^3.21.0 + '@tiptap/core': ^3.22.0 + '@tiptap/pm': ^3.22.0 - '@tiptap/pm@3.21.0': - resolution: {integrity: sha512-I3sNo7oMMsR6FFz1ecvPb9uCF0VQuS2WV67j8Io2M7DJicRWCE/GM5DaiYjTeWBbnByk6BuG0txoJATAqPVliQ==} + '@tiptap/pm@3.22.0': + resolution: {integrity: sha512-O9kpzNnFX5837kFevwAM8yr7ImLHu8noIwIpoci0AwfJjiBMzfZBejhbzxnKEfTpFWnkvZ8rWohlb6CQdJ6Crg==} - '@tiptap/react@3.21.0': - resolution: {integrity: sha512-p+OKJgxmFB3t5nY3mjaqjKaj8vJX9++OkdrZLRxYuG7ScAHemWraWQ25sgNZl1LDaRYrdnNYxx9MP0CXOSB6ew==} + '@tiptap/react@3.22.0': + resolution: {integrity: sha512-Jt2LxSbwIUTtp+2Fg27tnsUCC5XiRl084o+/uJTa5xcY4Fdce6rsrEDMjw5P7uYObhjdnBxr514tqd2bvC6LUw==} peerDependencies: - '@tiptap/core': ^3.21.0 - '@tiptap/pm': ^3.21.0 + '@tiptap/core': ^3.22.0 + '@tiptap/pm': ^3.22.0 '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 '@types/react-dom': ^17.0.0 || ^18.0.0 || ^19.0.0 react: ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 - '@tiptap/starter-kit@3.21.0': - resolution: {integrity: sha512-w7fWxglDtqXFBgRYH+LforJyUboSAQllnWQbGVSTyX4rsICqZjkb3f6CTSUWpGoGKmlmbb2ZpEuoik7tur9d8Q==} + '@tiptap/starter-kit@3.22.0': + resolution: {integrity: sha512-3V0RysviBKbsvzHuupM30ftb/WLogSgINtIbmGQHZK4Ta1YzwzW63nAWPGKOoFw8r1HRFFO6LjtqrT6iJsPnzQ==} '@tootallnate/quickjs-emscripten@0.23.0': resolution: {integrity: sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==} @@ -3879,6 +3901,9 @@ packages: '@tybys/wasm-util@0.10.1': resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} + '@types/aria-query@5.0.4': + resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} + '@types/babel__core@7.20.5': resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} @@ -3971,9 +3996,15 @@ packages: '@types/webidl-conversions@7.0.3': resolution: {integrity: sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==} + '@types/whatwg-mimetype@3.0.2': + resolution: {integrity: sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA==} + '@types/whatwg-url@13.0.0': resolution: {integrity: sha512-N8WXpbE6Wgri7KUSvrmQcqrMllKZ9uxkYWMt+mCSGwNc0Hsw9VQTW7ApqI4XNrx6/SaM2QQJCzMPDEXE058s+Q==} + '@types/ws@8.18.1': + resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} + '@types/yargs-parser@21.0.3': resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} @@ -3983,43 +4014,43 @@ packages: '@types/yauzl@2.10.3': resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} - '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260329.1': - resolution: {integrity: sha512-zS1thDk7luD82nXVwvMd97F7FgxAE6jGtSmnHeXdaQ+6hJQcQLOVkfUdaehhdodqKDapWA2jEURxQAYjDGvv3g==} + '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260401.1': + resolution: {integrity: sha512-9PCc1D4/zLic30g1upOw6ZmUl98fnrXYRv5wIZ6fxm1zZAObieRKUX3Jbr8M9N8iQQFxPIZPniIScsxAbmbJvw==} cpu: [arm64] os: [darwin] - '@typescript/native-preview-darwin-x64@7.0.0-dev.20260329.1': - resolution: {integrity: sha512-3IJ2qmpjQ1OXpZNUhJRjF1+SbDuqGC14Ug8DjWJlPBp06isi1fcJph90f5qW//FxEsNnJPYRcNwpP0A2RbTASg==} + '@typescript/native-preview-darwin-x64@7.0.0-dev.20260401.1': + resolution: {integrity: sha512-wwzca1KrjSVC6ApXfITsg/wF4GGbhVYebc7zChpuyi+phrHfw6ThTPB5XFUH4nA32vqw0Hn/6KACipMgzg8GPA==} cpu: [x64] os: [darwin] - '@typescript/native-preview-linux-arm64@7.0.0-dev.20260329.1': - resolution: {integrity: sha512-gQb6SjB5JlUKDaDuz6mv/m+/OBWVDlcjHINFOykBZZYZtgxBx6nEDjLrT8TiJRjmHEG6hSbv+yisUL9IThWycA==} + '@typescript/native-preview-linux-arm64@7.0.0-dev.20260401.1': + resolution: {integrity: sha512-1hgKibGi4QZF1J0hKltgY4nj4yKDmI4Ang5ar80I+YeUdGxV/fP2kU3bJang7QtHuSso6W+a52SF62zgqbzdow==} cpu: [arm64] os: [linux] - '@typescript/native-preview-linux-arm@7.0.0-dev.20260329.1': - resolution: {integrity: sha512-WKSSJrH611DFFAg6YCkgbnkdy0a4RRpzvDpNXtPzLTbMYC5oJdq3Dpvncx5nrJvGh4J4yvzXoMxraGPyygqGLw==} + '@typescript/native-preview-linux-arm@7.0.0-dev.20260401.1': + resolution: {integrity: sha512-bbIkRZYjtyoyCJ3wFES7qn3EwYO5Go1hxArL5X5oWiBmUHq5gMIxTZDv5mpWxopVf9Eyh4ErHefXjf1s4J+6Ag==} cpu: [arm] os: [linux] - '@typescript/native-preview-linux-x64@7.0.0-dev.20260329.1': - resolution: {integrity: sha512-kg4r+ssxoEWruBynUg9bFMdcMpo5NupzAPqNBlV8uWbmYGZjaPLonFWAi9ZZMiVJY/x5ZQ9GBl6xskwLdd3PJQ==} + '@typescript/native-preview-linux-x64@7.0.0-dev.20260401.1': + resolution: {integrity: sha512-1ysZ4c/Wa3RYIlrwVceYlhb1m1hxQ4P2x92valZXH0bNWEPb+oiQ4Yf35O/vi5h8zDdX/ZQ59vivYl27cF1VVA==} cpu: [x64] os: [linux] - '@typescript/native-preview-win32-arm64@7.0.0-dev.20260329.1': - resolution: {integrity: sha512-Qi4lddVxl5MG7Tk67gYhCFnoqqLGd4TvaI8RN4qHFjt3GV8s6c+0cQGsJXJnVgMx27qbyDTdsyAa2pvb42rYcQ==} + '@typescript/native-preview-win32-arm64@7.0.0-dev.20260401.1': + resolution: {integrity: sha512-fZYLCRe36y1BuzRFFpU2/RQ212l6Y1dccRMh8oTB8HlAVAAwtbkb6cjEn0Ablj4Dy16+Ih8R9uHsxPLNhtKw1Q==} cpu: [arm64] os: [win32] - '@typescript/native-preview-win32-x64@7.0.0-dev.20260329.1': - resolution: {integrity: sha512-+k5+usuB8HZ6Xc+enLdb95ZJd25bQqsnI1zXxfRCHP+RS9mxs70Mi9ezQz3lKOLZFFXShSH7iW9iulm8KwVzCQ==} + '@typescript/native-preview-win32-x64@7.0.0-dev.20260401.1': + resolution: {integrity: sha512-I6ses4SjWvpbvSpm1BPFRrDeqrzu7JTchJG/a26iwwmTLv4fAGLc5/o6Kv9Naygozop1W3KOcVM5i3A9oxiIjQ==} cpu: [x64] os: [win32] - '@typescript/native-preview@7.0.0-dev.20260329.1': - resolution: {integrity: sha512-v5lJ0TgSt2m9yVk2xoj9+NH/gTDeWTLaWGPx6MJsUKOYd6bmCJhHbMcWmb8d/zlfhE9ffpixUKYj62CdYfriqA==} + '@typescript/native-preview@7.0.0-dev.20260401.1': + resolution: {integrity: sha512-xJcN9WlY/P6xKjCMH4+DTzZSj/EKR6H9avuqUKs4eKyPEvaQ4bX+9Ys3Vl2qhlUaD7IRWY7HN7db0LHAGlWRSA==} hasBin: true '@typespec/ts-http-runtime@0.3.3': @@ -4083,6 +4114,21 @@ packages: babel-plugin-react-compiler: optional: true + '@vitest/coverage-v8@4.1.2': + resolution: {integrity: sha512-sPK//PHO+kAkScb8XITeB1bf7fsk85Km7+rt4eeuRR3VS1/crD47cmV5wicisJmjNdfeokTZwjMk4Mj2d58Mgg==} + peerDependencies: + '@vitest/browser': 4.1.2 + vitest: 4.1.2 + peerDependenciesMeta: + '@vitest/browser': + optional: true + + '@vitest/pretty-format@4.1.2': + resolution: {integrity: sha512-dwQga8aejqeuB+TvXCMzSQemvV9hNEtDDpgUKDzOmNQayl2OG241PSWeJwKRH3CiC+sESrmoFd49rfnq7T4RnA==} + + '@vitest/utils@4.1.2': + resolution: {integrity: sha512-xw2/TiX82lQHA06cgbqRKFb5lCAy3axQ4H4SoUFhUsg+wztiet+co86IAMDtF6Vm1hc7J6j09oh/rgDn+JdKIQ==} + '@voidzero-dev/vite-plus-core@0.1.12': resolution: {integrity: sha512-j8YNe7A+8JcSoddztf5whvom/yJ7OKUO3Y5a3UoLIUmOL8YEKVv5nPANrxJ7eaFfHJoMnBEwzBpq1YVZ+H3uPA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -4242,8 +4288,8 @@ packages: peerDependencies: ai: ^6.0.137 - ai@6.0.141: - resolution: {integrity: sha512-+GomGQWaId3xN0wcugUW/H7xMMaFkID2PiS7K/Wugj45G3efv0BXhQ3psRZoQVoRbOpdNoUqcK/KTB+FR4h6qg==} + ai@6.0.142: + resolution: {integrity: sha512-ZoxAsnTL/dFg5WdcwC8QNhKVlLtqwwT3I7p/4i8IJJP+6ZwqF1ljuwMsAsPYYvppZ+RzUxjxxFGb1cbEhNH3dg==} engines: {node: '>=18'} peerDependencies: zod: ^3.25.76 || ^4.1.8 @@ -4290,6 +4336,9 @@ packages: resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} engines: {node: '>=10'} + aria-query@5.3.0: + resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} + array-buffer-byte-length@1.0.2: resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} engines: {node: '>= 0.4'} @@ -4313,6 +4362,9 @@ packages: resolution: {integrity: sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==} engines: {node: '>=4'} + ast-v8-to-istanbul@1.0.0: + resolution: {integrity: sha512-1fSfIwuDICFA4LKkCzRPO7F0hzFf0B7+Xqrl27ynQaa+Rh0e1Es0v6kWHPott3lU10AyAr7oKHa65OppjLn3Rg==} + async-function@1.0.0: resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} engines: {node: '>= 0.4'} @@ -4942,6 +4994,10 @@ packages: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + destr@2.0.5: resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==} @@ -4963,6 +5019,9 @@ packages: resolution: {integrity: sha512-ZJja9/KBUuFC109sCMzovoq2GR2wCG/AuxivjA+OHj/q0TEgJIm3S7yrlUxIy3B+bV8YDj/BiHfWyrRFmyWpDQ==} engines: {node: '>=10'} + dom-accessibility-api@0.5.16: + resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} + dom-serializer@2.0.0: resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} @@ -5271,6 +5330,9 @@ packages: estree-walker@2.0.2: resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + esutils@2.0.3: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} @@ -5528,9 +5590,6 @@ packages: resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} engines: {node: '>= 0.4'} - get-tsconfig@4.13.6: - resolution: {integrity: sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==} - get-tsconfig@4.13.7: resolution: {integrity: sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q==} @@ -5587,6 +5646,10 @@ packages: crossws: optional: true + happy-dom@20.8.9: + resolution: {integrity: sha512-Tz23LR9T9jOGVZm2x1EPdXqwA37G/owYMxRwU0E4miurAtFsPMQ1d2Jc2okUaSjZqAFz2oEn3FLXC5a0a+siyA==} + engines: {node: '>=20.0.0'} + has-bigints@1.1.0: resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} engines: {node: '>= 0.4'} @@ -5631,6 +5694,9 @@ packages: resolution: {integrity: sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + htmlparser2@10.1.0: resolution: {integrity: sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==} @@ -5931,6 +5997,18 @@ packages: resolution: {integrity: sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==} engines: {node: '>=20'} + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + + istanbul-reports@3.2.0: + resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} + engines: {node: '>=8'} + jackspeak@4.2.3: resolution: {integrity: sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==} engines: {node: 20 || >=22} @@ -5965,6 +6043,9 @@ packages: js-sha256@0.10.1: resolution: {integrity: sha512-5obBtsz9301ULlsgggLg542s/jqtddfOpV5KJc4hajc9JV8GeY2gZHSVpYBn4nWqAUTJ9v+xwtbJ1mIBgIH5Vw==} + js-tokens@10.0.0: + resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -6038,8 +6119,8 @@ packages: resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} engines: {node: '>=6'} - knip@6.1.0: - resolution: {integrity: sha512-n5eVbJP7HXmwTsiJcELWJe2O1ESxyCTNxJzRTIECDYDTM465qnqk7fL2dv6ae3NUFvFWorZvGlh9mcwxwJ5Xgw==} + knip@6.2.0: + resolution: {integrity: sha512-4OMUMJARvNble8e8TeFv12flp4fKzAITrQec1eKO4g2eA4HnNqEa8CXy2UOPLjuYuAETpe0N0r25jF9yY9FLig==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true @@ -6204,12 +6285,23 @@ packages: resolution: {integrity: sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA==} engines: {bun: '>=1.0.0', deno: '>=1.30.0', node: '>=8.0.0'} + lz-string@1.5.0: + resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} + hasBin: true + magic-string@0.25.9: resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==} magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + magicast@0.5.2: + resolution: {integrity: sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==} + + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + make-fetch-happen@15.0.4: resolution: {integrity: sha512-vM2sG+wbVeVGYcCm16mM3d5fuem9oC28n436HjsGO3LcxoTI8LNVa4rwZDn3f76+cWyT4GGJDxjTYU1I2nr6zw==} engines: {node: ^20.17.0 || >=22.9.0} @@ -6507,8 +6599,8 @@ packages: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} - npm-check-updates@19.6.6: - resolution: {integrity: sha512-AvlRcnlUEyBEJfblUSjYMJwYKvCIWDRuCDa6x3hyUMTMkI3kslmFm0LDqwgzQfshfNh0Z3ouKiA4fLjRN7HejQ==} + npm-check-updates@20.0.0: + resolution: {integrity: sha512-qCs02x51irGf0okCttwv8lHEO2NxT903IJ2bKpG82kIzkm6pfT3CoWB5YIvqY/wi/DdnYRfI7eVfCYYymQgvCg==} engines: {node: '>=20.0.0', npm: '>=8.12.1'} hasBin: true @@ -6831,6 +6923,10 @@ packages: resolution: {integrity: sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==} engines: {node: ^14.13.1 || >=16.0.0} + pretty-format@27.5.1: + resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + pretty-format@29.7.0: resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -7010,6 +7106,9 @@ packages: react: '>=16.8.0' react-dom: '>=16.8.0' + react-is@17.0.2: + resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + react-is@18.3.1: resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} @@ -7294,8 +7393,8 @@ packages: setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} - shadcn@4.1.1: - resolution: {integrity: sha512-nBj+7LYC9kzV9v9QmRPpoOhfW4KctJVQejywdAt/K+K+z4RYlJOcO2a4AaF7elrRWkfCbgXeGK02liV0KB9HvQ==} + shadcn@4.1.2: + resolution: {integrity: sha512-qNQcCavkbYsgBj+X09tF2bTcwRd8abR880bsFkDU2kMqceMCLAm5c+cLg7kWDhfh1H9g08knpQ5ZEf6y/co16g==} hasBin: true sharp@0.34.5: @@ -7404,8 +7503,8 @@ packages: resolution: {integrity: sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==} engines: {node: '>= 0.6'} - srvx@0.11.13: - resolution: {integrity: sha512-oknN6qduuMPafxKtHucUeG32Q963pjriA5g3/Bl05cwEsUe5VVbIU4qR9LrALHbipSCyBe+VmfDGGydqazDRkw==} + srvx@0.11.14: + resolution: {integrity: sha512-mx+pKrWJCzo5m6uXqyB7n4VA81mpdFRroSWsVTQTYqCZE65hFJ+jtVIeyhtL2/kvtDMrHdbA0hWEUh/vu0+Viw==} engines: {node: '>=20.16.0'} hasBin: true @@ -7602,6 +7701,10 @@ packages: resolution: {integrity: sha512-Pugqs6M0m7Lv1I7FtxN4aoyToKg1C4tu+/381vH35y8oENM/Ai7f7C4StcoK4/+BSw9ebcS8jRiVrORFKCALLw==} engines: {node: ^20.0.0 || >=22.0.0} + tinyrainbow@3.1.0: + resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} + engines: {node: '>=14.0.0'} + tldts-core@6.1.86: resolution: {integrity: sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==} @@ -7985,6 +8088,10 @@ packages: whatwg-fetch@3.6.20: resolution: {integrity: sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==} + whatwg-mimetype@3.0.0: + resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==} + engines: {node: '>=12'} + whatwg-mimetype@4.0.0: resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} engines: {node: '>=18'} @@ -8246,20 +8353,20 @@ snapshots: '@ai-sdk/provider-utils': 4.0.21(zod@4.3.6) zod: 4.3.6 - '@ai-sdk/gateway@3.0.83(zod@4.3.6)': + '@ai-sdk/gateway@3.0.84(zod@4.3.6)': dependencies: '@ai-sdk/provider': 3.0.8 '@ai-sdk/provider-utils': 4.0.21(zod@4.3.6) '@vercel/oidc': 3.1.0 zod: 4.3.6 - '@ai-sdk/google@3.0.53(zod@4.3.6)': + '@ai-sdk/google@3.0.55(zod@4.3.6)': dependencies: '@ai-sdk/provider': 3.0.8 '@ai-sdk/provider-utils': 4.0.21(zod@4.3.6) zod: 4.3.6 - '@ai-sdk/openai@3.0.48(zod@4.3.6)': + '@ai-sdk/openai@3.0.49(zod@4.3.6)': dependencies: '@ai-sdk/provider': 3.0.8 '@ai-sdk/provider-utils': 4.0.21(zod@4.3.6) @@ -8290,6 +8397,7 @@ snapshots: '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) '@csstools/css-tokenizer': 4.0.0 lru-cache: 11.2.7 + optional: true '@asamuzakjp/dom-selector@7.0.3': dependencies: @@ -8298,8 +8406,10 @@ snapshots: css-tree: 3.2.1 is-potential-custom-element-name: 1.0.1 lru-cache: 11.2.7 + optional: true - '@asamuzakjp/nwsapi@2.3.9': {} + '@asamuzakjp/nwsapi@2.3.9': + optional: true '@authenio/xml-encryption@2.0.2': dependencies: @@ -8354,31 +8464,31 @@ snapshots: '@smithy/util-utf8': 2.3.0 tslib: 2.8.1 - '@aws-sdk/client-s3@3.1019.0': + '@aws-sdk/client-s3@3.1022.0': dependencies: '@aws-crypto/sha1-browser': 5.2.0 '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/core': 3.973.25 - '@aws-sdk/credential-provider-node': 3.972.27 + '@aws-sdk/core': 3.973.26 + '@aws-sdk/credential-provider-node': 3.972.29 '@aws-sdk/middleware-bucket-endpoint': 3.972.8 '@aws-sdk/middleware-expect-continue': 3.972.8 - '@aws-sdk/middleware-flexible-checksums': 3.974.5 + '@aws-sdk/middleware-flexible-checksums': 3.974.6 '@aws-sdk/middleware-host-header': 3.972.8 '@aws-sdk/middleware-location-constraint': 3.972.8 '@aws-sdk/middleware-logger': 3.972.8 '@aws-sdk/middleware-recursion-detection': 3.972.9 - '@aws-sdk/middleware-sdk-s3': 3.972.26 + '@aws-sdk/middleware-sdk-s3': 3.972.27 '@aws-sdk/middleware-ssec': 3.972.8 - '@aws-sdk/middleware-user-agent': 3.972.26 + '@aws-sdk/middleware-user-agent': 3.972.28 '@aws-sdk/region-config-resolver': 3.972.10 - '@aws-sdk/signature-v4-multi-region': 3.996.14 + '@aws-sdk/signature-v4-multi-region': 3.996.15 '@aws-sdk/types': 3.973.6 '@aws-sdk/util-endpoints': 3.996.5 '@aws-sdk/util-user-agent-browser': 3.972.8 - '@aws-sdk/util-user-agent-node': 3.973.12 + '@aws-sdk/util-user-agent-node': 3.973.14 '@smithy/config-resolver': 4.4.13 - '@smithy/core': 3.23.12 + '@smithy/core': 3.23.13 '@smithy/eventstream-serde-browser': 4.2.12 '@smithy/eventstream-serde-config-resolver': 4.3.12 '@smithy/eventstream-serde-node': 4.2.12 @@ -8389,41 +8499,41 @@ snapshots: '@smithy/invalid-dependency': 4.2.12 '@smithy/md5-js': 4.2.12 '@smithy/middleware-content-length': 4.2.12 - '@smithy/middleware-endpoint': 4.4.27 - '@smithy/middleware-retry': 4.4.44 - '@smithy/middleware-serde': 4.2.15 + '@smithy/middleware-endpoint': 4.4.28 + '@smithy/middleware-retry': 4.4.46 + '@smithy/middleware-serde': 4.2.16 '@smithy/middleware-stack': 4.2.12 '@smithy/node-config-provider': 4.3.12 - '@smithy/node-http-handler': 4.5.0 + '@smithy/node-http-handler': 4.5.1 '@smithy/protocol-http': 5.3.12 - '@smithy/smithy-client': 4.12.7 + '@smithy/smithy-client': 4.12.8 '@smithy/types': 4.13.1 '@smithy/url-parser': 4.2.12 '@smithy/util-base64': 4.3.2 '@smithy/util-body-length-browser': 4.2.2 '@smithy/util-body-length-node': 4.2.3 - '@smithy/util-defaults-mode-browser': 4.3.43 - '@smithy/util-defaults-mode-node': 4.2.47 + '@smithy/util-defaults-mode-browser': 4.3.44 + '@smithy/util-defaults-mode-node': 4.2.48 '@smithy/util-endpoints': 3.3.3 '@smithy/util-middleware': 4.2.12 - '@smithy/util-retry': 4.2.12 - '@smithy/util-stream': 4.5.20 + '@smithy/util-retry': 4.2.13 + '@smithy/util-stream': 4.5.21 '@smithy/util-utf8': 4.2.2 - '@smithy/util-waiter': 4.2.13 + '@smithy/util-waiter': 4.2.14 tslib: 2.8.1 transitivePeerDependencies: - aws-crt - '@aws-sdk/core@3.973.25': + '@aws-sdk/core@3.973.26': dependencies: '@aws-sdk/types': 3.973.6 '@aws-sdk/xml-builder': 3.972.16 - '@smithy/core': 3.23.12 + '@smithy/core': 3.23.13 '@smithy/node-config-provider': 4.3.12 '@smithy/property-provider': 4.2.12 '@smithy/protocol-http': 5.3.12 '@smithy/signature-v4': 5.3.12 - '@smithy/smithy-client': 4.12.7 + '@smithy/smithy-client': 4.12.8 '@smithy/types': 4.13.1 '@smithy/util-base64': 4.3.2 '@smithy/util-middleware': 4.2.12 @@ -8435,37 +8545,37 @@ snapshots: '@smithy/types': 4.13.1 tslib: 2.8.1 - '@aws-sdk/credential-provider-env@3.972.23': + '@aws-sdk/credential-provider-env@3.972.24': dependencies: - '@aws-sdk/core': 3.973.25 + '@aws-sdk/core': 3.973.26 '@aws-sdk/types': 3.973.6 '@smithy/property-provider': 4.2.12 '@smithy/types': 4.13.1 tslib: 2.8.1 - '@aws-sdk/credential-provider-http@3.972.25': + '@aws-sdk/credential-provider-http@3.972.26': dependencies: - '@aws-sdk/core': 3.973.25 + '@aws-sdk/core': 3.973.26 '@aws-sdk/types': 3.973.6 '@smithy/fetch-http-handler': 5.3.15 - '@smithy/node-http-handler': 4.5.0 + '@smithy/node-http-handler': 4.5.1 '@smithy/property-provider': 4.2.12 '@smithy/protocol-http': 5.3.12 - '@smithy/smithy-client': 4.12.7 + '@smithy/smithy-client': 4.12.8 '@smithy/types': 4.13.1 - '@smithy/util-stream': 4.5.20 + '@smithy/util-stream': 4.5.21 tslib: 2.8.1 - '@aws-sdk/credential-provider-ini@3.972.26': + '@aws-sdk/credential-provider-ini@3.972.28': dependencies: - '@aws-sdk/core': 3.973.25 - '@aws-sdk/credential-provider-env': 3.972.23 - '@aws-sdk/credential-provider-http': 3.972.25 - '@aws-sdk/credential-provider-login': 3.972.26 - '@aws-sdk/credential-provider-process': 3.972.23 - '@aws-sdk/credential-provider-sso': 3.972.26 - '@aws-sdk/credential-provider-web-identity': 3.972.26 - '@aws-sdk/nested-clients': 3.996.16 + '@aws-sdk/core': 3.973.26 + '@aws-sdk/credential-provider-env': 3.972.24 + '@aws-sdk/credential-provider-http': 3.972.26 + '@aws-sdk/credential-provider-login': 3.972.28 + '@aws-sdk/credential-provider-process': 3.972.24 + '@aws-sdk/credential-provider-sso': 3.972.28 + '@aws-sdk/credential-provider-web-identity': 3.972.28 + '@aws-sdk/nested-clients': 3.996.18 '@aws-sdk/types': 3.973.6 '@smithy/credential-provider-imds': 4.2.12 '@smithy/property-provider': 4.2.12 @@ -8475,10 +8585,10 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/credential-provider-login@3.972.26': + '@aws-sdk/credential-provider-login@3.972.28': dependencies: - '@aws-sdk/core': 3.973.25 - '@aws-sdk/nested-clients': 3.996.16 + '@aws-sdk/core': 3.973.26 + '@aws-sdk/nested-clients': 3.996.18 '@aws-sdk/types': 3.973.6 '@smithy/property-provider': 4.2.12 '@smithy/protocol-http': 5.3.12 @@ -8488,14 +8598,14 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/credential-provider-node@3.972.27': + '@aws-sdk/credential-provider-node@3.972.29': dependencies: - '@aws-sdk/credential-provider-env': 3.972.23 - '@aws-sdk/credential-provider-http': 3.972.25 - '@aws-sdk/credential-provider-ini': 3.972.26 - '@aws-sdk/credential-provider-process': 3.972.23 - '@aws-sdk/credential-provider-sso': 3.972.26 - '@aws-sdk/credential-provider-web-identity': 3.972.26 + '@aws-sdk/credential-provider-env': 3.972.24 + '@aws-sdk/credential-provider-http': 3.972.26 + '@aws-sdk/credential-provider-ini': 3.972.28 + '@aws-sdk/credential-provider-process': 3.972.24 + '@aws-sdk/credential-provider-sso': 3.972.28 + '@aws-sdk/credential-provider-web-identity': 3.972.28 '@aws-sdk/types': 3.973.6 '@smithy/credential-provider-imds': 4.2.12 '@smithy/property-provider': 4.2.12 @@ -8505,20 +8615,20 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/credential-provider-process@3.972.23': + '@aws-sdk/credential-provider-process@3.972.24': dependencies: - '@aws-sdk/core': 3.973.25 + '@aws-sdk/core': 3.973.26 '@aws-sdk/types': 3.973.6 '@smithy/property-provider': 4.2.12 '@smithy/shared-ini-file-loader': 4.4.7 '@smithy/types': 4.13.1 tslib: 2.8.1 - '@aws-sdk/credential-provider-sso@3.972.26': + '@aws-sdk/credential-provider-sso@3.972.28': dependencies: - '@aws-sdk/core': 3.973.25 - '@aws-sdk/nested-clients': 3.996.16 - '@aws-sdk/token-providers': 3.1019.0 + '@aws-sdk/core': 3.973.26 + '@aws-sdk/nested-clients': 3.996.18 + '@aws-sdk/token-providers': 3.1021.0 '@aws-sdk/types': 3.973.6 '@smithy/property-provider': 4.2.12 '@smithy/shared-ini-file-loader': 4.4.7 @@ -8527,10 +8637,10 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/credential-provider-web-identity@3.972.26': + '@aws-sdk/credential-provider-web-identity@3.972.28': dependencies: - '@aws-sdk/core': 3.973.25 - '@aws-sdk/nested-clients': 3.996.16 + '@aws-sdk/core': 3.973.26 + '@aws-sdk/nested-clients': 3.996.18 '@aws-sdk/types': 3.973.6 '@smithy/property-provider': 4.2.12 '@smithy/shared-ini-file-loader': 4.4.7 @@ -8556,12 +8666,12 @@ snapshots: '@smithy/types': 4.13.1 tslib: 2.8.1 - '@aws-sdk/middleware-flexible-checksums@3.974.5': + '@aws-sdk/middleware-flexible-checksums@3.974.6': dependencies: '@aws-crypto/crc32': 5.2.0 '@aws-crypto/crc32c': 5.2.0 '@aws-crypto/util': 5.2.0 - '@aws-sdk/core': 3.973.25 + '@aws-sdk/core': 3.973.26 '@aws-sdk/crc64-nvme': 3.972.5 '@aws-sdk/types': 3.973.6 '@smithy/is-array-buffer': 4.2.2 @@ -8569,7 +8679,7 @@ snapshots: '@smithy/protocol-http': 5.3.12 '@smithy/types': 4.13.1 '@smithy/util-middleware': 4.2.12 - '@smithy/util-stream': 4.5.20 + '@smithy/util-stream': 4.5.21 '@smithy/util-utf8': 4.2.2 tslib: 2.8.1 @@ -8600,20 +8710,20 @@ snapshots: '@smithy/types': 4.13.1 tslib: 2.8.1 - '@aws-sdk/middleware-sdk-s3@3.972.26': + '@aws-sdk/middleware-sdk-s3@3.972.27': dependencies: - '@aws-sdk/core': 3.973.25 + '@aws-sdk/core': 3.973.26 '@aws-sdk/types': 3.973.6 '@aws-sdk/util-arn-parser': 3.972.3 - '@smithy/core': 3.23.12 + '@smithy/core': 3.23.13 '@smithy/node-config-provider': 4.3.12 '@smithy/protocol-http': 5.3.12 '@smithy/signature-v4': 5.3.12 - '@smithy/smithy-client': 4.12.7 + '@smithy/smithy-client': 4.12.8 '@smithy/types': 4.13.1 '@smithy/util-config-provider': 4.2.2 '@smithy/util-middleware': 4.2.12 - '@smithy/util-stream': 4.5.20 + '@smithy/util-stream': 4.5.21 '@smithy/util-utf8': 4.2.2 tslib: 2.8.1 @@ -8623,55 +8733,55 @@ snapshots: '@smithy/types': 4.13.1 tslib: 2.8.1 - '@aws-sdk/middleware-user-agent@3.972.26': + '@aws-sdk/middleware-user-agent@3.972.28': dependencies: - '@aws-sdk/core': 3.973.25 + '@aws-sdk/core': 3.973.26 '@aws-sdk/types': 3.973.6 '@aws-sdk/util-endpoints': 3.996.5 - '@smithy/core': 3.23.12 + '@smithy/core': 3.23.13 '@smithy/protocol-http': 5.3.12 '@smithy/types': 4.13.1 - '@smithy/util-retry': 4.2.12 + '@smithy/util-retry': 4.2.13 tslib: 2.8.1 - '@aws-sdk/nested-clients@3.996.16': + '@aws-sdk/nested-clients@3.996.18': dependencies: '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/core': 3.973.25 + '@aws-sdk/core': 3.973.26 '@aws-sdk/middleware-host-header': 3.972.8 '@aws-sdk/middleware-logger': 3.972.8 '@aws-sdk/middleware-recursion-detection': 3.972.9 - '@aws-sdk/middleware-user-agent': 3.972.26 + '@aws-sdk/middleware-user-agent': 3.972.28 '@aws-sdk/region-config-resolver': 3.972.10 '@aws-sdk/types': 3.973.6 '@aws-sdk/util-endpoints': 3.996.5 '@aws-sdk/util-user-agent-browser': 3.972.8 - '@aws-sdk/util-user-agent-node': 3.973.12 + '@aws-sdk/util-user-agent-node': 3.973.14 '@smithy/config-resolver': 4.4.13 - '@smithy/core': 3.23.12 + '@smithy/core': 3.23.13 '@smithy/fetch-http-handler': 5.3.15 '@smithy/hash-node': 4.2.12 '@smithy/invalid-dependency': 4.2.12 '@smithy/middleware-content-length': 4.2.12 - '@smithy/middleware-endpoint': 4.4.27 - '@smithy/middleware-retry': 4.4.44 - '@smithy/middleware-serde': 4.2.15 + '@smithy/middleware-endpoint': 4.4.28 + '@smithy/middleware-retry': 4.4.46 + '@smithy/middleware-serde': 4.2.16 '@smithy/middleware-stack': 4.2.12 '@smithy/node-config-provider': 4.3.12 - '@smithy/node-http-handler': 4.5.0 + '@smithy/node-http-handler': 4.5.1 '@smithy/protocol-http': 5.3.12 - '@smithy/smithy-client': 4.12.7 + '@smithy/smithy-client': 4.12.8 '@smithy/types': 4.13.1 '@smithy/url-parser': 4.2.12 '@smithy/util-base64': 4.3.2 '@smithy/util-body-length-browser': 4.2.2 '@smithy/util-body-length-node': 4.2.3 - '@smithy/util-defaults-mode-browser': 4.3.43 - '@smithy/util-defaults-mode-node': 4.2.47 + '@smithy/util-defaults-mode-browser': 4.3.44 + '@smithy/util-defaults-mode-node': 4.2.48 '@smithy/util-endpoints': 3.3.3 '@smithy/util-middleware': 4.2.12 - '@smithy/util-retry': 4.2.12 + '@smithy/util-retry': 4.2.13 '@smithy/util-utf8': 4.2.2 tslib: 2.8.1 transitivePeerDependencies: @@ -8685,19 +8795,19 @@ snapshots: '@smithy/types': 4.13.1 tslib: 2.8.1 - '@aws-sdk/signature-v4-multi-region@3.996.14': + '@aws-sdk/signature-v4-multi-region@3.996.15': dependencies: - '@aws-sdk/middleware-sdk-s3': 3.972.26 + '@aws-sdk/middleware-sdk-s3': 3.972.27 '@aws-sdk/types': 3.973.6 '@smithy/protocol-http': 5.3.12 '@smithy/signature-v4': 5.3.12 '@smithy/types': 4.13.1 tslib: 2.8.1 - '@aws-sdk/token-providers@3.1019.0': + '@aws-sdk/token-providers@3.1021.0': dependencies: - '@aws-sdk/core': 3.973.25 - '@aws-sdk/nested-clients': 3.996.16 + '@aws-sdk/core': 3.973.26 + '@aws-sdk/nested-clients': 3.996.18 '@aws-sdk/types': 3.973.6 '@smithy/property-provider': 4.2.12 '@smithy/shared-ini-file-loader': 4.4.7 @@ -8734,9 +8844,9 @@ snapshots: bowser: 2.14.1 tslib: 2.8.1 - '@aws-sdk/util-user-agent-node@3.973.12': + '@aws-sdk/util-user-agent-node@3.973.14': dependencies: - '@aws-sdk/middleware-user-agent': 3.972.26 + '@aws-sdk/middleware-user-agent': 3.972.28 '@aws-sdk/types': 3.973.6 '@smithy/node-config-provider': 4.3.12 '@smithy/types': 4.13.1 @@ -9611,11 +9721,13 @@ snapshots: optionalDependencies: '@types/react': 19.2.14 - '@better-auth/api-key@1.5.6(@better-auth/core@1.5.6(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.2(zod@4.3.6))(jose@6.2.2)(kysely@0.28.14)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(better-auth@1.5.6(d57c8314811a2c76a791cf28e230a6db))': + '@bcoe/v8-coverage@1.0.2': {} + + '@better-auth/api-key@1.5.6(@better-auth/core@1.5.6(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.2(zod@4.3.6))(jose@6.2.2)(kysely@0.28.14)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(better-auth@1.5.6(9d65c2ad022ab83f20e12e7f626dac52))': dependencies: '@better-auth/core': 1.5.6(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.2(zod@4.3.6))(jose@6.2.2)(kysely@0.28.14)(nanostores@1.1.1) '@better-auth/utils': 0.3.1 - better-auth: 1.5.6(d57c8314811a2c76a791cf28e230a6db) + better-auth: 1.5.6(9d65c2ad022ab83f20e12e7f626dac52) zod: 4.3.6 '@better-auth/core@1.5.6(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.2(zod@4.3.6))(jose@6.2.2)(kysely@0.28.14)(nanostores@1.1.1)': @@ -9638,12 +9750,12 @@ snapshots: optionalDependencies: drizzle-orm: 1.0.0-beta.20(@electric-sql/pglite@0.3.15)(@opentelemetry/api@1.9.0)(@types/mssql@9.1.9(@azure/core-client@1.10.1))(@types/pg@8.20.0)(mssql@11.0.1(@azure/core-client@1.10.1))(mysql2@3.15.3)(pg@8.20.0)(postgres@3.4.7)(valibot@1.2.0(typescript@5.9.3))(zod@4.3.6) - '@better-auth/infra@0.1.13(@better-auth/core@1.5.6(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.2(zod@4.3.6))(jose@6.2.2)(kysely@0.28.14)(nanostores@1.1.1))(@better-auth/sso@1.5.5(@better-auth/core@1.5.6(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.2(zod@4.3.6))(jose@6.2.2)(kysely@0.28.14)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(better-auth@1.5.6(d57c8314811a2c76a791cf28e230a6db))(better-call@1.3.2(zod@4.3.6)))(better-auth@1.5.6(d57c8314811a2c76a791cf28e230a6db))(zod@4.3.6)': + '@better-auth/infra@0.1.13(@better-auth/core@1.5.6(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.2(zod@4.3.6))(jose@6.2.2)(kysely@0.28.14)(nanostores@1.1.1))(@better-auth/sso@1.5.5(@better-auth/core@1.5.6(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.2(zod@4.3.6))(jose@6.2.2)(kysely@0.28.14)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(better-auth@1.5.6(9d65c2ad022ab83f20e12e7f626dac52))(better-call@1.3.2(zod@4.3.6)))(better-auth@1.5.6(9d65c2ad022ab83f20e12e7f626dac52))(zod@4.3.6)': dependencies: '@better-auth/core': 1.5.6(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.2(zod@4.3.6))(jose@6.2.2)(kysely@0.28.14)(nanostores@1.1.1) - '@better-auth/sso': 1.5.5(@better-auth/core@1.5.6(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.2(zod@4.3.6))(jose@6.2.2)(kysely@0.28.14)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(better-auth@1.5.6(d57c8314811a2c76a791cf28e230a6db))(better-call@1.3.2(zod@4.3.6)) + '@better-auth/sso': 1.5.5(@better-auth/core@1.5.6(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.2(zod@4.3.6))(jose@6.2.2)(kysely@0.28.14)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(better-auth@1.5.6(9d65c2ad022ab83f20e12e7f626dac52))(better-call@1.3.2(zod@4.3.6)) '@better-fetch/fetch': 1.1.21 - better-auth: 1.5.6(d57c8314811a2c76a791cf28e230a6db) + better-auth: 1.5.6(9d65c2ad022ab83f20e12e7f626dac52) better-call: 1.3.4(zod@4.3.6) jose: 6.2.2 libphonenumber-js: 1.12.40 @@ -9668,12 +9780,12 @@ snapshots: optionalDependencies: mongodb: 7.1.0(socks@2.8.7) - '@better-auth/oauth-provider@1.5.6(@better-auth/core@1.5.6(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.2(zod@4.3.6))(jose@6.2.2)(kysely@0.28.14)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-auth@1.5.6(d57c8314811a2c76a791cf28e230a6db))(better-call@1.3.2(zod@4.3.6))': + '@better-auth/oauth-provider@1.5.6(@better-auth/core@1.5.6(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.2(zod@4.3.6))(jose@6.2.2)(kysely@0.28.14)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-auth@1.5.6(9d65c2ad022ab83f20e12e7f626dac52))(better-call@1.3.2(zod@4.3.6))': dependencies: '@better-auth/core': 1.5.6(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.2(zod@4.3.6))(jose@6.2.2)(kysely@0.28.14)(nanostores@1.1.1) '@better-auth/utils': 0.3.1 '@better-fetch/fetch': 1.1.21 - better-auth: 1.5.6(d57c8314811a2c76a791cf28e230a6db) + better-auth: 1.5.6(9d65c2ad022ab83f20e12e7f626dac52) better-call: 1.3.2(zod@4.3.6) jose: 6.2.2 zod: 4.3.6 @@ -9686,12 +9798,12 @@ snapshots: '@prisma/client': 7.4.2(prisma@7.4.2(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3) prisma: 7.4.2(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3) - '@better-auth/sso@1.5.5(@better-auth/core@1.5.6(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.2(zod@4.3.6))(jose@6.2.2)(kysely@0.28.14)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(better-auth@1.5.6(d57c8314811a2c76a791cf28e230a6db))(better-call@1.3.2(zod@4.3.6))': + '@better-auth/sso@1.5.5(@better-auth/core@1.5.6(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.2(zod@4.3.6))(jose@6.2.2)(kysely@0.28.14)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(better-auth@1.5.6(9d65c2ad022ab83f20e12e7f626dac52))(better-call@1.3.2(zod@4.3.6))': dependencies: '@better-auth/core': 1.5.6(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.2(zod@4.3.6))(jose@6.2.2)(kysely@0.28.14)(nanostores@1.1.1) '@better-auth/utils': 0.3.1 '@better-fetch/fetch': 1.1.21 - better-auth: 1.5.6(d57c8314811a2c76a791cf28e230a6db) + better-auth: 1.5.6(9d65c2ad022ab83f20e12e7f626dac52) better-call: 1.3.2(zod@4.3.6) fast-xml-parser: 5.4.2 jose: 6.2.2 @@ -9712,6 +9824,7 @@ snapshots: '@bramus/specificity@2.4.2': dependencies: css-tree: 3.2.1 + optional: true '@chevrotain/cst-dts-gen@10.5.0': dependencies: @@ -9732,12 +9845,14 @@ snapshots: '@chevrotain/utils@10.5.0': optional: true - '@csstools/color-helpers@6.0.2': {} + '@csstools/color-helpers@6.0.2': + optional: true '@csstools/css-calc@3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': dependencies: '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) '@csstools/css-tokenizer': 4.0.0 + optional: true '@csstools/css-color-parser@4.0.2(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': dependencies: @@ -9745,16 +9860,20 @@ snapshots: '@csstools/css-calc': 3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) '@csstools/css-tokenizer': 4.0.0 + optional: true '@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0)': dependencies: '@csstools/css-tokenizer': 4.0.0 + optional: true '@csstools/css-syntax-patches-for-csstree@1.1.1(css-tree@3.2.1)': optionalDependencies: css-tree: 3.2.1 + optional: true - '@csstools/css-tokenizer@4.0.0': {} + '@csstools/css-tokenizer@4.0.0': + optional: true '@dnd-kit/accessibility@3.1.1(react@19.2.4)': dependencies: @@ -9987,6 +10106,7 @@ snapshots: '@exodus/bytes@1.15.0(@noble/hashes@2.0.1)': optionalDependencies: '@noble/hashes': 2.0.1 + optional: true '@floating-ui/core@1.7.5': dependencies: @@ -10303,7 +10423,7 @@ snapshots: dependencies: moo: 0.5.3 - '@modelcontextprotocol/sdk@1.28.0(zod@3.25.76)': + '@modelcontextprotocol/sdk@1.29.0(zod@3.25.76)': dependencies: '@hono/node-server': 1.19.10(hono@4.12.5) ajv: 8.18.0 @@ -10325,7 +10445,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@modelcontextprotocol/sdk@1.28.0(zod@4.3.6)': + '@modelcontextprotocol/sdk@1.29.0(zod@4.3.6)': dependencies: '@hono/node-server': 1.19.10(hono@4.12.5) ajv: 8.18.0 @@ -10473,12 +10593,12 @@ snapshots: '@orpc/interop@1.13.13': {} - '@orpc/json-schema@1.13.13(@opentelemetry/api@1.9.0)(crossws@0.4.4(srvx@0.11.13))(ws@8.19.0)': + '@orpc/json-schema@1.13.13(@opentelemetry/api@1.9.0)(crossws@0.4.4(srvx@0.11.14))(ws@8.19.0)': dependencies: '@orpc/contract': 1.13.13(@opentelemetry/api@1.9.0) '@orpc/interop': 1.13.13 - '@orpc/openapi': 1.13.13(@opentelemetry/api@1.9.0)(crossws@0.4.4(srvx@0.11.13))(ws@8.19.0) - '@orpc/server': 1.13.13(@opentelemetry/api@1.9.0)(crossws@0.4.4(srvx@0.11.13))(ws@8.19.0) + '@orpc/openapi': 1.13.13(@opentelemetry/api@1.9.0)(crossws@0.4.4(srvx@0.11.14))(ws@8.19.0) + '@orpc/server': 1.13.13(@opentelemetry/api@1.9.0)(crossws@0.4.4(srvx@0.11.14))(ws@8.19.0) '@orpc/shared': 1.13.13(@opentelemetry/api@1.9.0) json-schema-typed: 8.0.2 transitivePeerDependencies: @@ -10496,13 +10616,13 @@ snapshots: transitivePeerDependencies: - '@opentelemetry/api' - '@orpc/openapi@1.13.13(@opentelemetry/api@1.9.0)(crossws@0.4.4(srvx@0.11.13))(ws@8.19.0)': + '@orpc/openapi@1.13.13(@opentelemetry/api@1.9.0)(crossws@0.4.4(srvx@0.11.14))(ws@8.19.0)': dependencies: '@orpc/client': 1.13.13(@opentelemetry/api@1.9.0) '@orpc/contract': 1.13.13(@opentelemetry/api@1.9.0) '@orpc/interop': 1.13.13 '@orpc/openapi-client': 1.13.13(@opentelemetry/api@1.9.0) - '@orpc/server': 1.13.13(@opentelemetry/api@1.9.0)(crossws@0.4.4(srvx@0.11.13))(ws@8.19.0) + '@orpc/server': 1.13.13(@opentelemetry/api@1.9.0)(crossws@0.4.4(srvx@0.11.14))(ws@8.19.0) '@orpc/shared': 1.13.13(@opentelemetry/api@1.9.0) '@orpc/standard-server': 1.13.13(@opentelemetry/api@1.9.0) json-schema-typed: 8.0.2 @@ -10513,7 +10633,7 @@ snapshots: - fastify - ws - '@orpc/server@1.13.13(@opentelemetry/api@1.9.0)(crossws@0.4.4(srvx@0.11.13))(ws@8.19.0)': + '@orpc/server@1.13.13(@opentelemetry/api@1.9.0)(crossws@0.4.4(srvx@0.11.14))(ws@8.19.0)': dependencies: '@orpc/client': 1.13.13(@opentelemetry/api@1.9.0) '@orpc/contract': 1.13.13(@opentelemetry/api@1.9.0) @@ -10527,7 +10647,7 @@ snapshots: '@orpc/standard-server-peer': 1.13.13(@opentelemetry/api@1.9.0) cookie: 1.1.1 optionalDependencies: - crossws: 0.4.4(srvx@0.11.13) + crossws: 0.4.4(srvx@0.11.14) ws: 8.19.0 transitivePeerDependencies: - '@opentelemetry/api' @@ -10585,20 +10705,20 @@ snapshots: transitivePeerDependencies: - '@opentelemetry/api' - '@orpc/tanstack-query@1.13.13(@opentelemetry/api@1.9.0)(@orpc/client@1.13.13(@opentelemetry/api@1.9.0))(@tanstack/query-core@5.95.2)': + '@orpc/tanstack-query@1.13.13(@opentelemetry/api@1.9.0)(@orpc/client@1.13.13(@opentelemetry/api@1.9.0))(@tanstack/query-core@5.96.1)': dependencies: '@orpc/client': 1.13.13(@opentelemetry/api@1.9.0) '@orpc/shared': 1.13.13(@opentelemetry/api@1.9.0) - '@tanstack/query-core': 5.95.2 + '@tanstack/query-core': 5.96.1 transitivePeerDependencies: - '@opentelemetry/api' - '@orpc/zod@1.13.13(@opentelemetry/api@1.9.0)(@orpc/contract@1.13.13(@opentelemetry/api@1.9.0))(@orpc/server@1.13.13(@opentelemetry/api@1.9.0)(crossws@0.4.4(srvx@0.11.13))(ws@8.19.0))(crossws@0.4.4(srvx@0.11.13))(ws@8.19.0)(zod@4.3.6)': + '@orpc/zod@1.13.13(@opentelemetry/api@1.9.0)(@orpc/contract@1.13.13(@opentelemetry/api@1.9.0))(@orpc/server@1.13.13(@opentelemetry/api@1.9.0)(crossws@0.4.4(srvx@0.11.14))(ws@8.19.0))(crossws@0.4.4(srvx@0.11.14))(ws@8.19.0)(zod@4.3.6)': dependencies: '@orpc/contract': 1.13.13(@opentelemetry/api@1.9.0) - '@orpc/json-schema': 1.13.13(@opentelemetry/api@1.9.0)(crossws@0.4.4(srvx@0.11.13))(ws@8.19.0) - '@orpc/openapi': 1.13.13(@opentelemetry/api@1.9.0)(crossws@0.4.4(srvx@0.11.13))(ws@8.19.0) - '@orpc/server': 1.13.13(@opentelemetry/api@1.9.0)(crossws@0.4.4(srvx@0.11.13))(ws@8.19.0) + '@orpc/json-schema': 1.13.13(@opentelemetry/api@1.9.0)(crossws@0.4.4(srvx@0.11.14))(ws@8.19.0) + '@orpc/openapi': 1.13.13(@opentelemetry/api@1.9.0)(crossws@0.4.4(srvx@0.11.14))(ws@8.19.0) + '@orpc/server': 1.13.13(@opentelemetry/api@1.9.0)(crossws@0.4.4(srvx@0.11.14))(ws@8.19.0) '@orpc/shared': 1.13.13(@opentelemetry/api@1.9.0) escape-string-regexp: 5.0.0 wildcard-match: 5.1.4 @@ -11269,11 +11389,6 @@ snapshots: '@sindresorhus/transliterate@2.3.1': {} - '@smithy/abort-controller@4.2.12': - dependencies: - '@smithy/types': 4.13.1 - tslib: 2.8.1 - '@smithy/chunked-blob-reader-native@4.2.3': dependencies: '@smithy/util-base64': 4.3.2 @@ -11292,7 +11407,7 @@ snapshots: '@smithy/util-middleware': 4.2.12 tslib: 2.8.1 - '@smithy/core@3.23.12': + '@smithy/core@3.23.13': dependencies: '@smithy/protocol-http': 5.3.12 '@smithy/types': 4.13.1 @@ -11300,7 +11415,7 @@ snapshots: '@smithy/util-base64': 4.3.2 '@smithy/util-body-length-browser': 4.2.2 '@smithy/util-middleware': 4.2.12 - '@smithy/util-stream': 4.5.20 + '@smithy/util-stream': 4.5.21 '@smithy/util-utf8': 4.2.2 '@smithy/uuid': 1.1.2 tslib: 2.8.1 @@ -11396,10 +11511,10 @@ snapshots: '@smithy/types': 4.13.1 tslib: 2.8.1 - '@smithy/middleware-endpoint@4.4.27': + '@smithy/middleware-endpoint@4.4.28': dependencies: - '@smithy/core': 3.23.12 - '@smithy/middleware-serde': 4.2.15 + '@smithy/core': 3.23.13 + '@smithy/middleware-serde': 4.2.16 '@smithy/node-config-provider': 4.3.12 '@smithy/shared-ini-file-loader': 4.4.7 '@smithy/types': 4.13.1 @@ -11407,21 +11522,21 @@ snapshots: '@smithy/util-middleware': 4.2.12 tslib: 2.8.1 - '@smithy/middleware-retry@4.4.44': + '@smithy/middleware-retry@4.4.46': dependencies: '@smithy/node-config-provider': 4.3.12 '@smithy/protocol-http': 5.3.12 '@smithy/service-error-classification': 4.2.12 - '@smithy/smithy-client': 4.12.7 + '@smithy/smithy-client': 4.12.8 '@smithy/types': 4.13.1 '@smithy/util-middleware': 4.2.12 - '@smithy/util-retry': 4.2.12 + '@smithy/util-retry': 4.2.13 '@smithy/uuid': 1.1.2 tslib: 2.8.1 - '@smithy/middleware-serde@4.2.15': + '@smithy/middleware-serde@4.2.16': dependencies: - '@smithy/core': 3.23.12 + '@smithy/core': 3.23.13 '@smithy/protocol-http': 5.3.12 '@smithy/types': 4.13.1 tslib: 2.8.1 @@ -11438,9 +11553,8 @@ snapshots: '@smithy/types': 4.13.1 tslib: 2.8.1 - '@smithy/node-http-handler@4.5.0': + '@smithy/node-http-handler@4.5.1': dependencies: - '@smithy/abort-controller': 4.2.12 '@smithy/protocol-http': 5.3.12 '@smithy/querystring-builder': 4.2.12 '@smithy/types': 4.13.1 @@ -11487,14 +11601,14 @@ snapshots: '@smithy/util-utf8': 4.2.2 tslib: 2.8.1 - '@smithy/smithy-client@4.12.7': + '@smithy/smithy-client@4.12.8': dependencies: - '@smithy/core': 3.23.12 - '@smithy/middleware-endpoint': 4.4.27 + '@smithy/core': 3.23.13 + '@smithy/middleware-endpoint': 4.4.28 '@smithy/middleware-stack': 4.2.12 '@smithy/protocol-http': 5.3.12 '@smithy/types': 4.13.1 - '@smithy/util-stream': 4.5.20 + '@smithy/util-stream': 4.5.21 tslib: 2.8.1 '@smithy/types@4.13.1': @@ -11535,20 +11649,20 @@ snapshots: dependencies: tslib: 2.8.1 - '@smithy/util-defaults-mode-browser@4.3.43': + '@smithy/util-defaults-mode-browser@4.3.44': dependencies: '@smithy/property-provider': 4.2.12 - '@smithy/smithy-client': 4.12.7 + '@smithy/smithy-client': 4.12.8 '@smithy/types': 4.13.1 tslib: 2.8.1 - '@smithy/util-defaults-mode-node@4.2.47': + '@smithy/util-defaults-mode-node@4.2.48': dependencies: '@smithy/config-resolver': 4.4.13 '@smithy/credential-provider-imds': 4.2.12 '@smithy/node-config-provider': 4.3.12 '@smithy/property-provider': 4.2.12 - '@smithy/smithy-client': 4.12.7 + '@smithy/smithy-client': 4.12.8 '@smithy/types': 4.13.1 tslib: 2.8.1 @@ -11567,16 +11681,16 @@ snapshots: '@smithy/types': 4.13.1 tslib: 2.8.1 - '@smithy/util-retry@4.2.12': + '@smithy/util-retry@4.2.13': dependencies: '@smithy/service-error-classification': 4.2.12 '@smithy/types': 4.13.1 tslib: 2.8.1 - '@smithy/util-stream@4.5.20': + '@smithy/util-stream@4.5.21': dependencies: '@smithy/fetch-http-handler': 5.3.15 - '@smithy/node-http-handler': 4.5.0 + '@smithy/node-http-handler': 4.5.1 '@smithy/types': 4.13.1 '@smithy/util-base64': 4.3.2 '@smithy/util-buffer-from': 4.2.2 @@ -11598,9 +11712,8 @@ snapshots: '@smithy/util-buffer-from': 4.2.2 tslib: 2.8.1 - '@smithy/util-waiter@4.2.13': + '@smithy/util-waiter@4.2.14': dependencies: - '@smithy/abort-controller': 4.2.12 '@smithy/types': 4.13.1 tslib: 2.8.1 @@ -11695,62 +11808,62 @@ snapshots: '@tanstack/history@1.161.6': {} - '@tanstack/query-core@5.95.2': {} + '@tanstack/query-core@5.96.1': {} - '@tanstack/react-query@5.95.2(react@19.2.4)': + '@tanstack/react-query@5.96.1(react@19.2.4)': dependencies: - '@tanstack/query-core': 5.95.2 + '@tanstack/query-core': 5.96.1 react: 19.2.4 - '@tanstack/react-router-ssr-query@1.166.10(@tanstack/query-core@5.95.2)(@tanstack/react-query@5.95.2(react@19.2.4))(@tanstack/react-router@1.168.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@tanstack/router-core@1.168.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@tanstack/react-router-ssr-query@1.166.10(@tanstack/query-core@5.96.1)(@tanstack/react-query@5.96.1(react@19.2.4))(@tanstack/react-router@1.168.10(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@tanstack/router-core@1.168.9)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@tanstack/query-core': 5.95.2 - '@tanstack/react-query': 5.95.2(react@19.2.4) - '@tanstack/react-router': 1.168.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@tanstack/router-ssr-query-core': 1.167.0(@tanstack/query-core@5.95.2)(@tanstack/router-core@1.168.7) + '@tanstack/query-core': 5.96.1 + '@tanstack/react-query': 5.96.1(react@19.2.4) + '@tanstack/react-router': 1.168.10(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@tanstack/router-ssr-query-core': 1.167.0(@tanstack/query-core@5.96.1)(@tanstack/router-core@1.168.9) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) transitivePeerDependencies: - '@tanstack/router-core' - '@tanstack/react-router@1.168.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@tanstack/react-router@1.168.10(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@tanstack/history': 1.161.6 '@tanstack/react-store': 0.9.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@tanstack/router-core': 1.168.7 + '@tanstack/router-core': 1.168.9 isbot: 5.1.35 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - '@tanstack/react-start-client@1.166.23(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@tanstack/react-start-client@1.166.25(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@tanstack/react-router': 1.168.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@tanstack/router-core': 1.168.7 - '@tanstack/start-client-core': 1.167.7 + '@tanstack/react-router': 1.168.10(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@tanstack/router-core': 1.168.9 + '@tanstack/start-client-core': 1.167.9 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - '@tanstack/react-start-server@1.166.23(crossws@0.4.4(srvx@0.11.13))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@tanstack/react-start-server@1.166.25(crossws@0.4.4(srvx@0.11.14))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@tanstack/history': 1.161.6 - '@tanstack/react-router': 1.168.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@tanstack/router-core': 1.168.7 - '@tanstack/start-client-core': 1.167.7 - '@tanstack/start-server-core': 1.167.7(crossws@0.4.4(srvx@0.11.13)) + '@tanstack/react-router': 1.168.10(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@tanstack/router-core': 1.168.9 + '@tanstack/start-client-core': 1.167.9 + '@tanstack/start-server-core': 1.167.9(crossws@0.4.4(srvx@0.11.14)) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) transitivePeerDependencies: - crossws - '@tanstack/react-start@1.167.13(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.3)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(crossws@0.4.4(srvx@0.11.13))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@tanstack/react-start@1.167.16(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.3)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(crossws@0.4.4(srvx@0.11.14))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@tanstack/react-router': 1.168.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@tanstack/react-start-client': 1.166.23(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@tanstack/react-start-server': 1.166.23(crossws@0.4.4(srvx@0.11.13))(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@tanstack/react-router': 1.168.10(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@tanstack/react-start-client': 1.166.25(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@tanstack/react-start-server': 1.166.25(crossws@0.4.4(srvx@0.11.14))(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@tanstack/router-utils': 1.161.6 - '@tanstack/start-client-core': 1.167.7 - '@tanstack/start-plugin-core': 1.167.14(@tanstack/react-router@1.168.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.3)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(crossws@0.4.4(srvx@0.11.13)) - '@tanstack/start-server-core': 1.167.7(crossws@0.4.4(srvx@0.11.13)) + '@tanstack/start-client-core': 1.167.9 + '@tanstack/start-plugin-core': 1.167.17(@tanstack/react-router@1.168.10(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.3)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(crossws@0.4.4(srvx@0.11.14)) + '@tanstack/start-server-core': 1.167.9(crossws@0.4.4(srvx@0.11.14)) pathe: 2.0.3 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) @@ -11769,16 +11882,16 @@ snapshots: react-dom: 19.2.4(react@19.2.4) use-sync-external-store: 1.6.0(react@19.2.4) - '@tanstack/router-core@1.168.7': + '@tanstack/router-core@1.168.9': dependencies: '@tanstack/history': 1.161.6 cookie-es: 2.0.0 seroval: 1.5.0 seroval-plugins: 1.5.0(seroval@1.5.0) - '@tanstack/router-generator@1.166.22': + '@tanstack/router-generator@1.166.24': dependencies: - '@tanstack/router-core': 1.168.7 + '@tanstack/router-core': 1.168.9 '@tanstack/router-utils': 1.161.6 '@tanstack/virtual-file-routes': 1.161.7 prettier: 3.8.1 @@ -11789,7 +11902,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@tanstack/router-plugin@1.167.9(@tanstack/react-router@1.168.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.3)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))': + '@tanstack/router-plugin@1.167.12(@tanstack/react-router@1.168.10(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.3)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))': dependencies: '@babel/core': 7.29.0 '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) @@ -11797,23 +11910,23 @@ snapshots: '@babel/template': 7.28.6 '@babel/traverse': 7.29.0 '@babel/types': 7.29.0 - '@tanstack/router-core': 1.168.7 - '@tanstack/router-generator': 1.166.22 + '@tanstack/router-core': 1.168.9 + '@tanstack/router-generator': 1.166.24 '@tanstack/router-utils': 1.161.6 '@tanstack/virtual-file-routes': 1.161.7 chokidar: 3.6.0 unplugin: 2.3.11 zod: 3.25.76 optionalDependencies: - '@tanstack/react-router': 1.168.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@tanstack/react-router': 1.168.10(react-dom@19.2.4(react@19.2.4))(react@19.2.4) vite: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.3)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' transitivePeerDependencies: - supports-color - '@tanstack/router-ssr-query-core@1.167.0(@tanstack/query-core@5.95.2)(@tanstack/router-core@1.168.7)': + '@tanstack/router-ssr-query-core@1.167.0(@tanstack/query-core@5.96.1)(@tanstack/router-core@1.168.9)': dependencies: - '@tanstack/query-core': 5.95.2 - '@tanstack/router-core': 1.168.7 + '@tanstack/query-core': 5.96.1 + '@tanstack/router-core': 1.168.9 '@tanstack/router-utils@1.161.6': dependencies: @@ -11829,33 +11942,33 @@ snapshots: transitivePeerDependencies: - supports-color - '@tanstack/start-client-core@1.167.7': + '@tanstack/start-client-core@1.167.9': dependencies: - '@tanstack/router-core': 1.168.7 + '@tanstack/router-core': 1.168.9 '@tanstack/start-fn-stubs': 1.161.6 - '@tanstack/start-storage-context': 1.166.21 + '@tanstack/start-storage-context': 1.166.23 seroval: 1.5.0 '@tanstack/start-fn-stubs@1.161.6': {} - '@tanstack/start-plugin-core@1.167.14(@tanstack/react-router@1.168.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.3)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(crossws@0.4.4(srvx@0.11.13))': + '@tanstack/start-plugin-core@1.167.17(@tanstack/react-router@1.168.10(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.3)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(crossws@0.4.4(srvx@0.11.14))': dependencies: '@babel/code-frame': 7.27.1 '@babel/core': 7.29.0 '@babel/types': 7.29.0 '@rolldown/pluginutils': 1.0.0-beta.40 - '@tanstack/router-core': 1.168.7 - '@tanstack/router-generator': 1.166.22 - '@tanstack/router-plugin': 1.167.9(@tanstack/react-router@1.168.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.3)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)) + '@tanstack/router-core': 1.168.9 + '@tanstack/router-generator': 1.166.24 + '@tanstack/router-plugin': 1.167.12(@tanstack/react-router@1.168.10(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.3)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)) '@tanstack/router-utils': 1.161.6 - '@tanstack/start-client-core': 1.167.7 - '@tanstack/start-server-core': 1.167.7(crossws@0.4.4(srvx@0.11.13)) + '@tanstack/start-client-core': 1.167.9 + '@tanstack/start-server-core': 1.167.9(crossws@0.4.4(srvx@0.11.14)) cheerio: 1.2.0 exsolve: 1.0.8 pathe: 2.0.3 picomatch: 4.0.3 source-map: 0.7.6 - srvx: 0.11.13 + srvx: 0.11.14 tinyglobby: 0.2.15 ufo: 1.6.3 vite: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.3)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' @@ -11870,158 +11983,179 @@ snapshots: - vite-plugin-solid - webpack - '@tanstack/start-server-core@1.167.7(crossws@0.4.4(srvx@0.11.13))': + '@tanstack/start-server-core@1.167.9(crossws@0.4.4(srvx@0.11.14))': dependencies: '@tanstack/history': 1.161.6 - '@tanstack/router-core': 1.168.7 - '@tanstack/start-client-core': 1.167.7 - '@tanstack/start-storage-context': 1.166.21 - h3-v2: h3@2.0.1-rc.16(crossws@0.4.4(srvx@0.11.13)) + '@tanstack/router-core': 1.168.9 + '@tanstack/start-client-core': 1.167.9 + '@tanstack/start-storage-context': 1.166.23 + h3-v2: h3@2.0.1-rc.16(crossws@0.4.4(srvx@0.11.14)) seroval: 1.5.0 transitivePeerDependencies: - crossws - '@tanstack/start-storage-context@1.166.21': + '@tanstack/start-storage-context@1.166.23': dependencies: - '@tanstack/router-core': 1.168.7 + '@tanstack/router-core': 1.168.9 '@tanstack/store@0.9.3': {} '@tanstack/virtual-file-routes@1.161.7': {} - '@tanstack/zod-adapter@1.166.9(@tanstack/react-router@1.168.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(zod@4.3.6)': + '@tanstack/zod-adapter@1.166.9(@tanstack/react-router@1.168.10(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(zod@4.3.6)': dependencies: - '@tanstack/react-router': 1.168.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@tanstack/react-router': 1.168.10(react-dom@19.2.4(react@19.2.4))(react@19.2.4) zod: 4.3.6 '@tediousjs/connection-string@0.5.0': {} - '@tiptap/core@3.21.0(@tiptap/pm@3.21.0)': + '@testing-library/dom@10.4.1': dependencies: - '@tiptap/pm': 3.21.0 + '@babel/code-frame': 7.29.0 + '@babel/runtime': 7.28.6 + '@types/aria-query': 5.0.4 + aria-query: 5.3.0 + dom-accessibility-api: 0.5.16 + lz-string: 1.5.0 + picocolors: 1.1.1 + pretty-format: 27.5.1 - '@tiptap/extension-blockquote@3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))': + '@testing-library/react@16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@tiptap/core': 3.21.0(@tiptap/pm@3.21.0) + '@babel/runtime': 7.28.6 + '@testing-library/dom': 10.4.1 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@tiptap/extension-bold@3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))': + '@tiptap/core@3.22.0(@tiptap/pm@3.22.0)': dependencies: - '@tiptap/core': 3.21.0(@tiptap/pm@3.21.0) + '@tiptap/pm': 3.22.0 - '@tiptap/extension-bubble-menu@3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))(@tiptap/pm@3.21.0)': + '@tiptap/extension-blockquote@3.22.0(@tiptap/core@3.22.0(@tiptap/pm@3.22.0))': + dependencies: + '@tiptap/core': 3.22.0(@tiptap/pm@3.22.0) + + '@tiptap/extension-bold@3.22.0(@tiptap/core@3.22.0(@tiptap/pm@3.22.0))': + dependencies: + '@tiptap/core': 3.22.0(@tiptap/pm@3.22.0) + + '@tiptap/extension-bubble-menu@3.22.0(@tiptap/core@3.22.0(@tiptap/pm@3.22.0))(@tiptap/pm@3.22.0)': dependencies: '@floating-ui/dom': 1.7.6 - '@tiptap/core': 3.21.0(@tiptap/pm@3.21.0) - '@tiptap/pm': 3.21.0 + '@tiptap/core': 3.22.0(@tiptap/pm@3.22.0) + '@tiptap/pm': 3.22.0 optional: true - '@tiptap/extension-bullet-list@3.21.0(@tiptap/extension-list@3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))(@tiptap/pm@3.21.0))': + '@tiptap/extension-bullet-list@3.22.0(@tiptap/extension-list@3.22.0(@tiptap/core@3.22.0(@tiptap/pm@3.22.0))(@tiptap/pm@3.22.0))': dependencies: - '@tiptap/extension-list': 3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))(@tiptap/pm@3.21.0) + '@tiptap/extension-list': 3.22.0(@tiptap/core@3.22.0(@tiptap/pm@3.22.0))(@tiptap/pm@3.22.0) - '@tiptap/extension-code-block@3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))(@tiptap/pm@3.21.0)': + '@tiptap/extension-code-block@3.22.0(@tiptap/core@3.22.0(@tiptap/pm@3.22.0))(@tiptap/pm@3.22.0)': dependencies: - '@tiptap/core': 3.21.0(@tiptap/pm@3.21.0) - '@tiptap/pm': 3.21.0 + '@tiptap/core': 3.22.0(@tiptap/pm@3.22.0) + '@tiptap/pm': 3.22.0 - '@tiptap/extension-code@3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))': + '@tiptap/extension-code@3.22.0(@tiptap/core@3.22.0(@tiptap/pm@3.22.0))': dependencies: - '@tiptap/core': 3.21.0(@tiptap/pm@3.21.0) + '@tiptap/core': 3.22.0(@tiptap/pm@3.22.0) - '@tiptap/extension-document@3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))': + '@tiptap/extension-document@3.22.0(@tiptap/core@3.22.0(@tiptap/pm@3.22.0))': dependencies: - '@tiptap/core': 3.21.0(@tiptap/pm@3.21.0) + '@tiptap/core': 3.22.0(@tiptap/pm@3.22.0) - '@tiptap/extension-dropcursor@3.21.0(@tiptap/extensions@3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))(@tiptap/pm@3.21.0))': + '@tiptap/extension-dropcursor@3.22.0(@tiptap/extensions@3.22.0(@tiptap/core@3.22.0(@tiptap/pm@3.22.0))(@tiptap/pm@3.22.0))': dependencies: - '@tiptap/extensions': 3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))(@tiptap/pm@3.21.0) + '@tiptap/extensions': 3.22.0(@tiptap/core@3.22.0(@tiptap/pm@3.22.0))(@tiptap/pm@3.22.0) - '@tiptap/extension-floating-menu@3.21.0(@floating-ui/dom@1.7.6)(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))(@tiptap/pm@3.21.0)': + '@tiptap/extension-floating-menu@3.22.0(@floating-ui/dom@1.7.6)(@tiptap/core@3.22.0(@tiptap/pm@3.22.0))(@tiptap/pm@3.22.0)': dependencies: '@floating-ui/dom': 1.7.6 - '@tiptap/core': 3.21.0(@tiptap/pm@3.21.0) - '@tiptap/pm': 3.21.0 + '@tiptap/core': 3.22.0(@tiptap/pm@3.22.0) + '@tiptap/pm': 3.22.0 optional: true - '@tiptap/extension-gapcursor@3.21.0(@tiptap/extensions@3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))(@tiptap/pm@3.21.0))': + '@tiptap/extension-gapcursor@3.22.0(@tiptap/extensions@3.22.0(@tiptap/core@3.22.0(@tiptap/pm@3.22.0))(@tiptap/pm@3.22.0))': dependencies: - '@tiptap/extensions': 3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))(@tiptap/pm@3.21.0) + '@tiptap/extensions': 3.22.0(@tiptap/core@3.22.0(@tiptap/pm@3.22.0))(@tiptap/pm@3.22.0) - '@tiptap/extension-hard-break@3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))': + '@tiptap/extension-hard-break@3.22.0(@tiptap/core@3.22.0(@tiptap/pm@3.22.0))': dependencies: - '@tiptap/core': 3.21.0(@tiptap/pm@3.21.0) + '@tiptap/core': 3.22.0(@tiptap/pm@3.22.0) - '@tiptap/extension-heading@3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))': + '@tiptap/extension-heading@3.22.0(@tiptap/core@3.22.0(@tiptap/pm@3.22.0))': dependencies: - '@tiptap/core': 3.21.0(@tiptap/pm@3.21.0) + '@tiptap/core': 3.22.0(@tiptap/pm@3.22.0) - '@tiptap/extension-highlight@3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))': + '@tiptap/extension-highlight@3.22.0(@tiptap/core@3.22.0(@tiptap/pm@3.22.0))': dependencies: - '@tiptap/core': 3.21.0(@tiptap/pm@3.21.0) + '@tiptap/core': 3.22.0(@tiptap/pm@3.22.0) - '@tiptap/extension-horizontal-rule@3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))(@tiptap/pm@3.21.0)': + '@tiptap/extension-horizontal-rule@3.22.0(@tiptap/core@3.22.0(@tiptap/pm@3.22.0))(@tiptap/pm@3.22.0)': dependencies: - '@tiptap/core': 3.21.0(@tiptap/pm@3.21.0) - '@tiptap/pm': 3.21.0 + '@tiptap/core': 3.22.0(@tiptap/pm@3.22.0) + '@tiptap/pm': 3.22.0 - '@tiptap/extension-italic@3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))': + '@tiptap/extension-italic@3.22.0(@tiptap/core@3.22.0(@tiptap/pm@3.22.0))': dependencies: - '@tiptap/core': 3.21.0(@tiptap/pm@3.21.0) + '@tiptap/core': 3.22.0(@tiptap/pm@3.22.0) - '@tiptap/extension-link@3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))(@tiptap/pm@3.21.0)': + '@tiptap/extension-link@3.22.0(@tiptap/core@3.22.0(@tiptap/pm@3.22.0))(@tiptap/pm@3.22.0)': dependencies: - '@tiptap/core': 3.21.0(@tiptap/pm@3.21.0) - '@tiptap/pm': 3.21.0 + '@tiptap/core': 3.22.0(@tiptap/pm@3.22.0) + '@tiptap/pm': 3.22.0 linkifyjs: 4.3.2 - '@tiptap/extension-list-item@3.21.0(@tiptap/extension-list@3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))(@tiptap/pm@3.21.0))': + '@tiptap/extension-list-item@3.22.0(@tiptap/extension-list@3.22.0(@tiptap/core@3.22.0(@tiptap/pm@3.22.0))(@tiptap/pm@3.22.0))': dependencies: - '@tiptap/extension-list': 3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))(@tiptap/pm@3.21.0) + '@tiptap/extension-list': 3.22.0(@tiptap/core@3.22.0(@tiptap/pm@3.22.0))(@tiptap/pm@3.22.0) - '@tiptap/extension-list-keymap@3.21.0(@tiptap/extension-list@3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))(@tiptap/pm@3.21.0))': + '@tiptap/extension-list-keymap@3.22.0(@tiptap/extension-list@3.22.0(@tiptap/core@3.22.0(@tiptap/pm@3.22.0))(@tiptap/pm@3.22.0))': dependencies: - '@tiptap/extension-list': 3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))(@tiptap/pm@3.21.0) + '@tiptap/extension-list': 3.22.0(@tiptap/core@3.22.0(@tiptap/pm@3.22.0))(@tiptap/pm@3.22.0) - '@tiptap/extension-list@3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))(@tiptap/pm@3.21.0)': + '@tiptap/extension-list@3.22.0(@tiptap/core@3.22.0(@tiptap/pm@3.22.0))(@tiptap/pm@3.22.0)': dependencies: - '@tiptap/core': 3.21.0(@tiptap/pm@3.21.0) - '@tiptap/pm': 3.21.0 + '@tiptap/core': 3.22.0(@tiptap/pm@3.22.0) + '@tiptap/pm': 3.22.0 - '@tiptap/extension-ordered-list@3.21.0(@tiptap/extension-list@3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))(@tiptap/pm@3.21.0))': + '@tiptap/extension-ordered-list@3.22.0(@tiptap/extension-list@3.22.0(@tiptap/core@3.22.0(@tiptap/pm@3.22.0))(@tiptap/pm@3.22.0))': dependencies: - '@tiptap/extension-list': 3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))(@tiptap/pm@3.21.0) + '@tiptap/extension-list': 3.22.0(@tiptap/core@3.22.0(@tiptap/pm@3.22.0))(@tiptap/pm@3.22.0) - '@tiptap/extension-paragraph@3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))': + '@tiptap/extension-paragraph@3.22.0(@tiptap/core@3.22.0(@tiptap/pm@3.22.0))': dependencies: - '@tiptap/core': 3.21.0(@tiptap/pm@3.21.0) + '@tiptap/core': 3.22.0(@tiptap/pm@3.22.0) - '@tiptap/extension-strike@3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))': + '@tiptap/extension-strike@3.22.0(@tiptap/core@3.22.0(@tiptap/pm@3.22.0))': dependencies: - '@tiptap/core': 3.21.0(@tiptap/pm@3.21.0) + '@tiptap/core': 3.22.0(@tiptap/pm@3.22.0) - '@tiptap/extension-table@3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))(@tiptap/pm@3.21.0)': + '@tiptap/extension-table@3.22.0(@tiptap/core@3.22.0(@tiptap/pm@3.22.0))(@tiptap/pm@3.22.0)': dependencies: - '@tiptap/core': 3.21.0(@tiptap/pm@3.21.0) - '@tiptap/pm': 3.21.0 + '@tiptap/core': 3.22.0(@tiptap/pm@3.22.0) + '@tiptap/pm': 3.22.0 - '@tiptap/extension-text-align@3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))': + '@tiptap/extension-text-align@3.22.0(@tiptap/core@3.22.0(@tiptap/pm@3.22.0))': dependencies: - '@tiptap/core': 3.21.0(@tiptap/pm@3.21.0) + '@tiptap/core': 3.22.0(@tiptap/pm@3.22.0) - '@tiptap/extension-text@3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))': + '@tiptap/extension-text@3.22.0(@tiptap/core@3.22.0(@tiptap/pm@3.22.0))': dependencies: - '@tiptap/core': 3.21.0(@tiptap/pm@3.21.0) + '@tiptap/core': 3.22.0(@tiptap/pm@3.22.0) - '@tiptap/extension-underline@3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))': + '@tiptap/extension-underline@3.22.0(@tiptap/core@3.22.0(@tiptap/pm@3.22.0))': dependencies: - '@tiptap/core': 3.21.0(@tiptap/pm@3.21.0) + '@tiptap/core': 3.22.0(@tiptap/pm@3.22.0) - '@tiptap/extensions@3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))(@tiptap/pm@3.21.0)': + '@tiptap/extensions@3.22.0(@tiptap/core@3.22.0(@tiptap/pm@3.22.0))(@tiptap/pm@3.22.0)': dependencies: - '@tiptap/core': 3.21.0(@tiptap/pm@3.21.0) - '@tiptap/pm': 3.21.0 + '@tiptap/core': 3.22.0(@tiptap/pm@3.22.0) + '@tiptap/pm': 3.22.0 - '@tiptap/pm@3.21.0': + '@tiptap/pm@3.22.0': dependencies: prosemirror-changeset: 2.4.0 prosemirror-collab: 1.3.1 @@ -12042,10 +12176,10 @@ snapshots: prosemirror-transform: 1.11.0 prosemirror-view: 1.41.6 - '@tiptap/react@3.21.0(@floating-ui/dom@1.7.6)(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))(@tiptap/pm@3.21.0)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@tiptap/react@3.22.0(@floating-ui/dom@1.7.6)(@tiptap/core@3.22.0(@tiptap/pm@3.22.0))(@tiptap/pm@3.22.0)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@tiptap/core': 3.21.0(@tiptap/pm@3.21.0) - '@tiptap/pm': 3.21.0 + '@tiptap/core': 3.22.0(@tiptap/pm@3.22.0) + '@tiptap/pm': 3.22.0 '@types/react': 19.2.14 '@types/react-dom': 19.2.3(@types/react@19.2.14) '@types/use-sync-external-store': 0.0.6 @@ -12054,37 +12188,37 @@ snapshots: react-dom: 19.2.4(react@19.2.4) use-sync-external-store: 1.6.0(react@19.2.4) optionalDependencies: - '@tiptap/extension-bubble-menu': 3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))(@tiptap/pm@3.21.0) - '@tiptap/extension-floating-menu': 3.21.0(@floating-ui/dom@1.7.6)(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))(@tiptap/pm@3.21.0) + '@tiptap/extension-bubble-menu': 3.22.0(@tiptap/core@3.22.0(@tiptap/pm@3.22.0))(@tiptap/pm@3.22.0) + '@tiptap/extension-floating-menu': 3.22.0(@floating-ui/dom@1.7.6)(@tiptap/core@3.22.0(@tiptap/pm@3.22.0))(@tiptap/pm@3.22.0) transitivePeerDependencies: - '@floating-ui/dom' - '@tiptap/starter-kit@3.21.0': + '@tiptap/starter-kit@3.22.0': dependencies: - '@tiptap/core': 3.21.0(@tiptap/pm@3.21.0) - '@tiptap/extension-blockquote': 3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0)) - '@tiptap/extension-bold': 3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0)) - '@tiptap/extension-bullet-list': 3.21.0(@tiptap/extension-list@3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))(@tiptap/pm@3.21.0)) - '@tiptap/extension-code': 3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0)) - '@tiptap/extension-code-block': 3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))(@tiptap/pm@3.21.0) - '@tiptap/extension-document': 3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0)) - '@tiptap/extension-dropcursor': 3.21.0(@tiptap/extensions@3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))(@tiptap/pm@3.21.0)) - '@tiptap/extension-gapcursor': 3.21.0(@tiptap/extensions@3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))(@tiptap/pm@3.21.0)) - '@tiptap/extension-hard-break': 3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0)) - '@tiptap/extension-heading': 3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0)) - '@tiptap/extension-horizontal-rule': 3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))(@tiptap/pm@3.21.0) - '@tiptap/extension-italic': 3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0)) - '@tiptap/extension-link': 3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))(@tiptap/pm@3.21.0) - '@tiptap/extension-list': 3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))(@tiptap/pm@3.21.0) - '@tiptap/extension-list-item': 3.21.0(@tiptap/extension-list@3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))(@tiptap/pm@3.21.0)) - '@tiptap/extension-list-keymap': 3.21.0(@tiptap/extension-list@3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))(@tiptap/pm@3.21.0)) - '@tiptap/extension-ordered-list': 3.21.0(@tiptap/extension-list@3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))(@tiptap/pm@3.21.0)) - '@tiptap/extension-paragraph': 3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0)) - '@tiptap/extension-strike': 3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0)) - '@tiptap/extension-text': 3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0)) - '@tiptap/extension-underline': 3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0)) - '@tiptap/extensions': 3.21.0(@tiptap/core@3.21.0(@tiptap/pm@3.21.0))(@tiptap/pm@3.21.0) - '@tiptap/pm': 3.21.0 + '@tiptap/core': 3.22.0(@tiptap/pm@3.22.0) + '@tiptap/extension-blockquote': 3.22.0(@tiptap/core@3.22.0(@tiptap/pm@3.22.0)) + '@tiptap/extension-bold': 3.22.0(@tiptap/core@3.22.0(@tiptap/pm@3.22.0)) + '@tiptap/extension-bullet-list': 3.22.0(@tiptap/extension-list@3.22.0(@tiptap/core@3.22.0(@tiptap/pm@3.22.0))(@tiptap/pm@3.22.0)) + '@tiptap/extension-code': 3.22.0(@tiptap/core@3.22.0(@tiptap/pm@3.22.0)) + '@tiptap/extension-code-block': 3.22.0(@tiptap/core@3.22.0(@tiptap/pm@3.22.0))(@tiptap/pm@3.22.0) + '@tiptap/extension-document': 3.22.0(@tiptap/core@3.22.0(@tiptap/pm@3.22.0)) + '@tiptap/extension-dropcursor': 3.22.0(@tiptap/extensions@3.22.0(@tiptap/core@3.22.0(@tiptap/pm@3.22.0))(@tiptap/pm@3.22.0)) + '@tiptap/extension-gapcursor': 3.22.0(@tiptap/extensions@3.22.0(@tiptap/core@3.22.0(@tiptap/pm@3.22.0))(@tiptap/pm@3.22.0)) + '@tiptap/extension-hard-break': 3.22.0(@tiptap/core@3.22.0(@tiptap/pm@3.22.0)) + '@tiptap/extension-heading': 3.22.0(@tiptap/core@3.22.0(@tiptap/pm@3.22.0)) + '@tiptap/extension-horizontal-rule': 3.22.0(@tiptap/core@3.22.0(@tiptap/pm@3.22.0))(@tiptap/pm@3.22.0) + '@tiptap/extension-italic': 3.22.0(@tiptap/core@3.22.0(@tiptap/pm@3.22.0)) + '@tiptap/extension-link': 3.22.0(@tiptap/core@3.22.0(@tiptap/pm@3.22.0))(@tiptap/pm@3.22.0) + '@tiptap/extension-list': 3.22.0(@tiptap/core@3.22.0(@tiptap/pm@3.22.0))(@tiptap/pm@3.22.0) + '@tiptap/extension-list-item': 3.22.0(@tiptap/extension-list@3.22.0(@tiptap/core@3.22.0(@tiptap/pm@3.22.0))(@tiptap/pm@3.22.0)) + '@tiptap/extension-list-keymap': 3.22.0(@tiptap/extension-list@3.22.0(@tiptap/core@3.22.0(@tiptap/pm@3.22.0))(@tiptap/pm@3.22.0)) + '@tiptap/extension-ordered-list': 3.22.0(@tiptap/extension-list@3.22.0(@tiptap/core@3.22.0(@tiptap/pm@3.22.0))(@tiptap/pm@3.22.0)) + '@tiptap/extension-paragraph': 3.22.0(@tiptap/core@3.22.0(@tiptap/pm@3.22.0)) + '@tiptap/extension-strike': 3.22.0(@tiptap/core@3.22.0(@tiptap/pm@3.22.0)) + '@tiptap/extension-text': 3.22.0(@tiptap/core@3.22.0(@tiptap/pm@3.22.0)) + '@tiptap/extension-underline': 3.22.0(@tiptap/core@3.22.0(@tiptap/pm@3.22.0)) + '@tiptap/extensions': 3.22.0(@tiptap/core@3.22.0(@tiptap/pm@3.22.0))(@tiptap/pm@3.22.0) + '@tiptap/pm': 3.22.0 '@tootallnate/quickjs-emscripten@0.23.0': {} @@ -12099,6 +12233,8 @@ snapshots: tslib: 2.8.1 optional: true + '@types/aria-query@5.0.4': {} + '@types/babel__core@7.20.5': dependencies: '@babel/parser': 7.29.0 @@ -12210,11 +12346,17 @@ snapshots: '@types/webidl-conversions@7.0.3': optional: true + '@types/whatwg-mimetype@3.0.2': {} + '@types/whatwg-url@13.0.0': dependencies: '@types/webidl-conversions': 7.0.3 optional: true + '@types/ws@8.18.1': + dependencies: + '@types/node': 25.5.0 + '@types/yargs-parser@21.0.3': {} '@types/yargs@17.0.35': @@ -12226,36 +12368,36 @@ snapshots: '@types/node': 25.5.0 optional: true - '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260329.1': + '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260401.1': optional: true - '@typescript/native-preview-darwin-x64@7.0.0-dev.20260329.1': + '@typescript/native-preview-darwin-x64@7.0.0-dev.20260401.1': optional: true - '@typescript/native-preview-linux-arm64@7.0.0-dev.20260329.1': + '@typescript/native-preview-linux-arm64@7.0.0-dev.20260401.1': optional: true - '@typescript/native-preview-linux-arm@7.0.0-dev.20260329.1': + '@typescript/native-preview-linux-arm@7.0.0-dev.20260401.1': optional: true - '@typescript/native-preview-linux-x64@7.0.0-dev.20260329.1': + '@typescript/native-preview-linux-x64@7.0.0-dev.20260401.1': optional: true - '@typescript/native-preview-win32-arm64@7.0.0-dev.20260329.1': + '@typescript/native-preview-win32-arm64@7.0.0-dev.20260401.1': optional: true - '@typescript/native-preview-win32-x64@7.0.0-dev.20260329.1': + '@typescript/native-preview-win32-x64@7.0.0-dev.20260401.1': optional: true - '@typescript/native-preview@7.0.0-dev.20260329.1': + '@typescript/native-preview@7.0.0-dev.20260401.1': optionalDependencies: - '@typescript/native-preview-darwin-arm64': 7.0.0-dev.20260329.1 - '@typescript/native-preview-darwin-x64': 7.0.0-dev.20260329.1 - '@typescript/native-preview-linux-arm': 7.0.0-dev.20260329.1 - '@typescript/native-preview-linux-arm64': 7.0.0-dev.20260329.1 - '@typescript/native-preview-linux-x64': 7.0.0-dev.20260329.1 - '@typescript/native-preview-win32-arm64': 7.0.0-dev.20260329.1 - '@typescript/native-preview-win32-x64': 7.0.0-dev.20260329.1 + '@typescript/native-preview-darwin-arm64': 7.0.0-dev.20260401.1 + '@typescript/native-preview-darwin-x64': 7.0.0-dev.20260401.1 + '@typescript/native-preview-linux-arm': 7.0.0-dev.20260401.1 + '@typescript/native-preview-linux-arm64': 7.0.0-dev.20260401.1 + '@typescript/native-preview-linux-x64': 7.0.0-dev.20260401.1 + '@typescript/native-preview-win32-arm64': 7.0.0-dev.20260401.1 + '@typescript/native-preview-win32-x64': 7.0.0-dev.20260401.1 '@typespec/ts-http-runtime@0.3.3': dependencies: @@ -12318,6 +12460,30 @@ snapshots: optionalDependencies: '@rolldown/plugin-babel': 0.2.2(@babel/core@7.29.0)(@babel/runtime@7.28.6)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.3)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(rolldown@1.0.0-rc.9) + '@vitest/coverage-v8@4.1.2(@voidzero-dev/vite-plus-test@0.1.12(@opentelemetry/api@1.9.0)(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.3)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.3)(happy-dom@20.8.9)(jiti@2.6.1)(jsdom@29.0.1(@noble/hashes@2.0.1))(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))': + dependencies: + '@bcoe/v8-coverage': 1.0.2 + '@vitest/utils': 4.1.2 + ast-v8-to-istanbul: 1.0.0 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-reports: 3.2.0 + magicast: 0.5.2 + obug: 2.1.1 + std-env: 4.0.0 + tinyrainbow: 3.1.0 + vitest: '@voidzero-dev/vite-plus-test@0.1.12(@opentelemetry/api@1.9.0)(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.3)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.3)(happy-dom@20.8.9)(jiti@2.6.1)(jsdom@29.0.1(@noble/hashes@2.0.1))(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' + + '@vitest/pretty-format@4.1.2': + dependencies: + tinyrainbow: 3.1.0 + + '@vitest/utils@4.1.2': + dependencies: + '@vitest/pretty-format': 4.1.2 + convert-source-map: 2.0.0 + tinyrainbow: 3.1.0 + '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.3)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)': dependencies: '@oxc-project/runtime': 0.115.0 @@ -12346,7 +12512,7 @@ snapshots: '@voidzero-dev/vite-plus-linux-x64-gnu@0.1.12': optional: true - '@voidzero-dev/vite-plus-test@0.1.12(@opentelemetry/api@1.9.0)(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.3)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.3)(jiti@2.6.1)(jsdom@29.0.1(@noble/hashes@2.0.1))(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)': + '@voidzero-dev/vite-plus-test@0.1.12(@opentelemetry/api@1.9.0)(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.3)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.3)(happy-dom@20.8.9)(jiti@2.6.1)(jsdom@29.0.1(@noble/hashes@2.0.1))(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)': dependencies: '@standard-schema/spec': 1.1.0 '@types/chai': 5.2.3 @@ -12365,6 +12531,7 @@ snapshots: optionalDependencies: '@opentelemetry/api': 1.9.0 '@types/node': 25.5.0 + happy-dom: 20.8.9 jsdom: 29.0.1(@noble/hashes@2.0.1) transitivePeerDependencies: - '@arethetypeswrong/core' @@ -12412,19 +12579,19 @@ snapshots: agent-base@7.1.4: {} - ai-sdk-ollama@3.8.2(ai@6.0.141(zod@4.3.6))(zod@4.3.6): + ai-sdk-ollama@3.8.2(ai@6.0.142(zod@4.3.6))(zod@4.3.6): dependencies: '@ai-sdk/provider': 3.0.8 '@ai-sdk/provider-utils': 4.0.21(zod@4.3.6) - ai: 6.0.141(zod@4.3.6) + ai: 6.0.142(zod@4.3.6) jsonrepair: 3.13.3 ollama: 0.6.3 transitivePeerDependencies: - zod - ai@6.0.141(zod@4.3.6): + ai@6.0.142(zod@4.3.6): dependencies: - '@ai-sdk/gateway': 3.0.83(zod@4.3.6) + '@ai-sdk/gateway': 3.0.84(zod@4.3.6) '@ai-sdk/provider': 3.0.8 '@ai-sdk/provider-utils': 4.0.21(zod@4.3.6) '@opentelemetry/api': 1.9.0 @@ -12464,6 +12631,10 @@ snapshots: dependencies: tslib: 2.8.1 + aria-query@5.3.0: + dependencies: + dequal: 2.0.3 + array-buffer-byte-length@1.0.2: dependencies: call-bound: 1.0.4 @@ -12493,6 +12664,12 @@ snapshots: dependencies: tslib: 2.8.1 + ast-v8-to-istanbul@1.0.0: + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + estree-walker: 3.0.3 + js-tokens: 10.0.0 + async-function@1.0.0: {} async@3.2.6: {} @@ -12593,7 +12770,7 @@ snapshots: node-addon-api: 8.7.0 node-gyp-build: 4.8.4 - better-auth@1.5.6(d57c8314811a2c76a791cf28e230a6db): + better-auth@1.5.6(9d65c2ad022ab83f20e12e7f626dac52): dependencies: '@better-auth/core': 1.5.6(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.2(zod@4.3.6))(jose@6.2.2)(kysely@0.28.14)(nanostores@1.1.1) '@better-auth/drizzle-adapter': 1.5.6(@better-auth/core@1.5.6(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.2(zod@4.3.6))(jose@6.2.2)(kysely@0.28.14)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(drizzle-orm@1.0.0-beta.20(@electric-sql/pglite@0.3.15)(@opentelemetry/api@1.9.0)(@types/mssql@9.1.9(@azure/core-client@1.10.1))(@types/pg@8.20.0)(mssql@11.0.1(@azure/core-client@1.10.1))(mysql2@3.15.3)(pg@8.20.0)(postgres@3.4.7)(valibot@1.2.0(typescript@5.9.3))(zod@4.3.6)) @@ -12614,7 +12791,7 @@ snapshots: zod: 4.3.6 optionalDependencies: '@prisma/client': 7.4.2(prisma@7.4.2(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3) - '@tanstack/react-start': 1.167.13(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.3)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(crossws@0.4.4(srvx@0.11.13))(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@tanstack/react-start': 1.167.16(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.3)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(crossws@0.4.4(srvx@0.11.14))(react-dom@19.2.4(react@19.2.4))(react@19.2.4) drizzle-kit: 1.0.0-beta.20 drizzle-orm: 1.0.0-beta.20(@electric-sql/pglite@0.3.15)(@opentelemetry/api@1.9.0)(@types/mssql@9.1.9(@azure/core-client@1.10.1))(@types/pg@8.20.0)(mssql@11.0.1(@azure/core-client@1.10.1))(mysql2@3.15.3)(pg@8.20.0)(postgres@3.4.7)(valibot@1.2.0(typescript@5.9.3))(zod@4.3.6) mongodb: 7.1.0(socks@2.8.7) @@ -12623,7 +12800,7 @@ snapshots: prisma: 7.4.2(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - vitest: '@voidzero-dev/vite-plus-test@0.1.12(@opentelemetry/api@1.9.0)(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.3)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.3)(jiti@2.6.1)(jsdom@29.0.1(@noble/hashes@2.0.1))(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' + vitest: '@voidzero-dev/vite-plus-test@0.1.12(@opentelemetry/api@1.9.0)(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.3)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.3)(happy-dom@20.8.9)(jiti@2.6.1)(jsdom@29.0.1(@noble/hashes@2.0.1))(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' transitivePeerDependencies: - '@cloudflare/workers-types' - '@opentelemetry/api' @@ -12649,6 +12826,7 @@ snapshots: bidi-js@1.0.3: dependencies: require-from-string: 2.0.2 + optional: true binary-extensions@2.3.0: {} @@ -12992,9 +13170,9 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 - crossws@0.4.4(srvx@0.11.13): + crossws@0.4.4(srvx@0.11.14): optionalDependencies: - srvx: 0.11.13 + srvx: 0.11.14 crypto-random-string@2.0.0: {} @@ -13010,6 +13188,7 @@ snapshots: dependencies: mdn-data: 2.27.1 source-map-js: 1.2.1 + optional: true css-what@6.2.2: {} @@ -13027,6 +13206,7 @@ snapshots: whatwg-url: 16.0.1(@noble/hashes@2.0.1) transitivePeerDependencies: - '@noble/hashes' + optional: true data-view-buffer@1.0.2: dependencies: @@ -13058,7 +13238,8 @@ snapshots: dependencies: ms: 2.1.3 - decimal.js@10.6.0: {} + decimal.js@10.6.0: + optional: true dedent@1.7.2(babel-plugin-macros@3.1.0): optionalDependencies: @@ -13107,6 +13288,8 @@ snapshots: depd@2.0.0: {} + dequal@2.0.3: {} + destr@2.0.5: optional: true @@ -13127,6 +13310,8 @@ snapshots: xml: 1.0.1 xml-js: 1.6.11 + dom-accessibility-api@0.5.16: {} + dom-serializer@2.0.0: dependencies: domelementtype: 2.3.0 @@ -13249,9 +13434,9 @@ snapshots: env-runner@0.1.6: dependencies: - crossws: 0.4.4(srvx@0.11.13) + crossws: 0.4.4(srvx@0.11.14) httpxy: 0.3.1 - srvx: 0.11.13 + srvx: 0.11.14 error-ex@1.3.4: dependencies: @@ -13424,6 +13609,10 @@ snapshots: estree-walker@2.0.2: {} + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 + esutils@2.0.3: {} etag@1.8.1: {} @@ -13723,10 +13912,6 @@ snapshots: es-errors: 1.3.0 get-intrinsic: 1.3.0 - get-tsconfig@4.13.6: - dependencies: - resolve-pkg-maps: 1.0.0 - get-tsconfig@4.13.7: dependencies: resolve-pkg-maps: 1.0.0 @@ -13785,12 +13970,24 @@ snapshots: graphql@16.13.1: {} - h3@2.0.1-rc.16(crossws@0.4.4(srvx@0.11.13)): + h3@2.0.1-rc.16(crossws@0.4.4(srvx@0.11.14)): dependencies: rou3: 0.8.1 - srvx: 0.11.13 + srvx: 0.11.14 optionalDependencies: - crossws: 0.4.4(srvx@0.11.13) + crossws: 0.4.4(srvx@0.11.14) + + happy-dom@20.8.9: + dependencies: + '@types/node': 25.5.0 + '@types/whatwg-mimetype': 3.0.2 + '@types/ws': 8.18.1 + entities: 7.0.1 + whatwg-mimetype: 3.0.0 + ws: 8.19.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate has-bigints@1.1.0: {} @@ -13830,6 +14027,9 @@ snapshots: '@exodus/bytes': 1.15.0(@noble/hashes@2.0.1) transitivePeerDependencies: - '@noble/hashes' + optional: true + + html-escaper@2.0.2: {} htmlparser2@10.1.0: dependencies: @@ -14011,7 +14211,8 @@ snapshots: is-plain-obj@4.1.0: {} - is-potential-custom-element-name@1.0.1: {} + is-potential-custom-element-name@1.0.1: + optional: true is-promise@4.0.0: {} @@ -14087,6 +14288,19 @@ snapshots: isexe@4.0.0: {} + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-report@3.0.1: + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + + istanbul-reports@3.2.0: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + jackspeak@4.2.3: dependencies: '@isaacs/cliui': 9.0.0 @@ -14118,6 +14332,8 @@ snapshots: js-sha256@0.10.1: {} + js-tokens@10.0.0: {} + js-tokens@4.0.0: {} js-yaml@4.1.1: @@ -14151,6 +14367,7 @@ snapshots: xml-name-validator: 5.0.0 transitivePeerDependencies: - '@noble/hashes' + optional: true jsesc@3.1.0: {} @@ -14209,7 +14426,7 @@ snapshots: kleur@4.1.5: {} - knip@6.1.0: + knip@6.2.0: dependencies: '@nodelib/fs.walk': 1.2.8 fast-glob: 3.3.3 @@ -14332,7 +14549,8 @@ snapshots: lru-cache@11.2.6: {} - lru-cache@11.2.7: {} + lru-cache@11.2.7: + optional: true lru-cache@5.1.1: dependencies: @@ -14343,6 +14561,8 @@ snapshots: lru.min@1.1.4: optional: true + lz-string@1.5.0: {} + magic-string@0.25.9: dependencies: sourcemap-codec: 1.4.8 @@ -14351,6 +14571,16 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 + magicast@0.5.2: + dependencies: + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 + source-map-js: 1.2.1 + + make-dir@4.0.0: + dependencies: + semver: 7.7.4 + make-fetch-happen@15.0.4: dependencies: '@gar/promise-retry': 1.0.2 @@ -14380,7 +14610,8 @@ snapshots: math-intrinsics@1.1.0: {} - mdn-data@2.27.1: {} + mdn-data@2.27.1: + optional: true mdurl@2.0.0: {} @@ -14568,17 +14799,17 @@ snapshots: nitro@3.0.260311-beta(@azure/identity@4.13.0)(@electric-sql/pglite@0.3.15)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.3)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(chokidar@4.0.3)(dotenv@17.3.1)(drizzle-orm@1.0.0-beta.20(@electric-sql/pglite@0.3.15)(@opentelemetry/api@1.9.0)(@types/mssql@9.1.9(@azure/core-client@1.10.1))(@types/pg@8.20.0)(mssql@11.0.1(@azure/core-client@1.10.1))(mysql2@3.15.3)(pg@8.20.0)(postgres@3.4.7)(valibot@1.2.0(typescript@5.9.3))(zod@4.3.6))(giget@2.0.0)(jiti@2.6.1)(lru-cache@11.2.7)(mongodb@7.1.0(socks@2.8.7))(mysql2@3.15.3)(rollup@2.80.0): dependencies: consola: 3.4.2 - crossws: 0.4.4(srvx@0.11.13) + crossws: 0.4.4(srvx@0.11.14) db0: 0.3.4(@electric-sql/pglite@0.3.15)(drizzle-orm@1.0.0-beta.20(@electric-sql/pglite@0.3.15)(@opentelemetry/api@1.9.0)(@types/mssql@9.1.9(@azure/core-client@1.10.1))(@types/pg@8.20.0)(mssql@11.0.1(@azure/core-client@1.10.1))(mysql2@3.15.3)(pg@8.20.0)(postgres@3.4.7)(valibot@1.2.0(typescript@5.9.3))(zod@4.3.6))(mysql2@3.15.3) env-runner: 0.1.6 - h3: 2.0.1-rc.16(crossws@0.4.4(srvx@0.11.13)) + h3: 2.0.1-rc.16(crossws@0.4.4(srvx@0.11.14)) hookable: 6.1.0 nf3: 0.3.11 ocache: 0.1.2 ofetch: 2.0.0-alpha.3 ohash: 2.0.11 rolldown: 1.0.0-rc.9 - srvx: 0.11.13 + srvx: 0.11.14 unenv: 2.0.0-rc.24 unstorage: 2.0.0-alpha.6(@azure/identity@4.13.0)(chokidar@4.0.3)(db0@0.3.4(@electric-sql/pglite@0.3.15)(drizzle-orm@1.0.0-beta.20(@electric-sql/pglite@0.3.15)(@opentelemetry/api@1.9.0)(@types/mssql@9.1.9(@azure/core-client@1.10.1))(@types/pg@8.20.0)(mssql@11.0.1(@azure/core-client@1.10.1))(mysql2@3.15.3)(pg@8.20.0)(postgres@3.4.7)(valibot@1.2.0(typescript@5.9.3))(zod@4.3.6))(mysql2@3.15.3))(lru-cache@11.2.7)(mongodb@7.1.0(socks@2.8.7))(ofetch@2.0.0-alpha.3) optionalDependencies: @@ -14661,7 +14892,7 @@ snapshots: normalize-path@3.0.0: {} - npm-check-updates@19.6.6: {} + npm-check-updates@20.0.0: {} npm-run-path@4.0.1: dependencies: @@ -14941,6 +15172,7 @@ snapshots: parse5@8.0.0: dependencies: entities: 6.0.1 + optional: true parseurl@1.3.3: {} @@ -15062,6 +15294,12 @@ snapshots: pretty-bytes@6.1.1: {} + pretty-format@27.5.1: + dependencies: + ansi-regex: 5.0.1 + ansi-styles: 5.2.0 + react-is: 17.0.2 + pretty-format@29.7.0: dependencies: '@jest/schemas': 29.6.3 @@ -15306,6 +15544,8 @@ snapshots: react: 19.2.4 react-dom: 19.2.4(react@19.2.4) + react-is@17.0.2: {} + react-is@18.3.1: {} react-remove-scroll-bar@2.3.8(@types/react@19.2.14)(react@19.2.4): @@ -15567,6 +15807,7 @@ snapshots: saxes@6.0.0: dependencies: xmlchars: 2.2.0 + optional: true scheduler@0.27.0: {} @@ -15638,14 +15879,14 @@ snapshots: setprototypeof@1.2.0: {} - shadcn@4.1.1(@types/node@25.5.0)(babel-plugin-macros@3.1.0)(typescript@5.9.3): + shadcn@4.1.2(@types/node@25.5.0)(babel-plugin-macros@3.1.0)(typescript@5.9.3): dependencies: '@babel/core': 7.29.0 '@babel/parser': 7.29.0 '@babel/plugin-transform-typescript': 7.28.6(@babel/core@7.29.0) '@babel/preset-typescript': 7.28.5(@babel/core@7.29.0) '@dotenvx/dotenvx': 1.54.1 - '@modelcontextprotocol/sdk': 1.28.0(zod@3.25.76) + '@modelcontextprotocol/sdk': 1.29.0(zod@3.25.76) '@types/validate-npm-package-name': 4.0.2 browserslist: 4.28.1 commander: 14.0.3 @@ -15811,7 +16052,7 @@ snapshots: sqlstring@2.3.3: optional: true - srvx@0.11.13: {} + srvx@0.11.14: {} ssri@13.0.1: dependencies: @@ -15941,7 +16182,8 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - symbol-tree@3.2.4: {} + symbol-tree@3.2.4: + optional: true tabbable@6.4.0: {} @@ -16076,6 +16318,8 @@ snapshots: tinypool@2.1.0: {} + tinyrainbow@3.1.0: {} + tldts-core@6.1.86: {} tldts-core@7.0.25: {} @@ -16112,6 +16356,7 @@ snapshots: tr46@6.0.0: dependencies: punycode: 2.3.1 + optional: true ts-morph@26.0.0: dependencies: @@ -16131,7 +16376,7 @@ snapshots: tsx@4.21.0: dependencies: esbuild: 0.27.3 - get-tsconfig: 4.13.6 + get-tsconfig: 4.13.7 optionalDependencies: fsevents: 2.3.3 @@ -16315,11 +16560,11 @@ snapshots: transitivePeerDependencies: - supports-color - vite-plus@0.1.12(@opentelemetry/api@1.9.0)(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.3)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.3)(jiti@2.6.1)(jsdom@29.0.1(@noble/hashes@2.0.1))(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2): + vite-plus@0.1.12(@opentelemetry/api@1.9.0)(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.3)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.3)(happy-dom@20.8.9)(jiti@2.6.1)(jsdom@29.0.1(@noble/hashes@2.0.1))(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2): dependencies: '@oxc-project/types': 0.115.0 '@voidzero-dev/vite-plus-core': 0.1.12(@types/node@25.5.0)(esbuild@0.27.3)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2) - '@voidzero-dev/vite-plus-test': 0.1.12(@opentelemetry/api@1.9.0)(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.3)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.3)(jiti@2.6.1)(jsdom@29.0.1(@noble/hashes@2.0.1))(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2) + '@voidzero-dev/vite-plus-test': 0.1.12(@opentelemetry/api@1.9.0)(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.3)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.3)(happy-dom@20.8.9)(jiti@2.6.1)(jsdom@29.0.1(@noble/hashes@2.0.1))(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2) cac: 6.7.14 cross-spawn: 7.0.6 oxfmt: 0.40.0 @@ -16370,6 +16615,7 @@ snapshots: w3c-xmlserializer@5.0.0: dependencies: xml-name-validator: 5.0.0 + optional: true walk-up-path@4.0.0: {} @@ -16386,7 +16632,8 @@ snapshots: webidl-conversions@7.0.0: optional: true - webidl-conversions@8.0.1: {} + webidl-conversions@8.0.1: + optional: true webpack-virtual-modules@0.6.2: {} @@ -16396,9 +16643,12 @@ snapshots: whatwg-fetch@3.6.20: {} + whatwg-mimetype@3.0.0: {} + whatwg-mimetype@4.0.0: {} - whatwg-mimetype@5.0.0: {} + whatwg-mimetype@5.0.0: + optional: true whatwg-url@14.2.0: dependencies: @@ -16413,6 +16663,7 @@ snapshots: webidl-conversions: 8.0.1 transitivePeerDependencies: - '@noble/hashes' + optional: true whatwg-url@7.1.0: dependencies: @@ -16625,7 +16876,8 @@ snapshots: dependencies: sax: 1.6.0 - xml-name-validator@5.0.0: {} + xml-name-validator@5.0.0: + optional: true xml@1.0.1: {} @@ -16636,7 +16888,8 @@ snapshots: '@oozcitak/util': 10.0.0 js-yaml: 4.1.1 - xmlchars@2.2.0: {} + xmlchars@2.2.0: + optional: true xpath@0.0.32: {} diff --git a/scripts/migration/resume.ts b/scripts/migration/resume.ts index b2fb34bc7..7f9ce8487 100644 --- a/scripts/migration/resume.ts +++ b/scripts/migration/resume.ts @@ -192,8 +192,7 @@ export async function migrateResumes() { process.exit(0); }; - process.on("SIGINT", handleShutdown); - process.on("SIGTERM", handleShutdown); + process.on("exit", handleShutdown); // Initialize the importer const importer = new ReactiveResumeV4JSONImporter(); @@ -465,8 +464,7 @@ export async function migrateResumes() { } // Remove signal handlers - process.off("SIGINT", handleShutdown); - process.off("SIGTERM", handleShutdown); + process.off("exit", handleShutdown); const migrationEnd = performance.now(); const migrationDurationMs = migrationEnd - migrationStart; diff --git a/scripts/migration/user.ts b/scripts/migration/user.ts index 646fc961a..109967dce 100644 --- a/scripts/migration/user.ts +++ b/scripts/migration/user.ts @@ -186,8 +186,7 @@ export async function migrateUsers() { process.exit(0); }; - process.on("SIGINT", handleShutdown); - process.on("SIGTERM", handleShutdown); + process.on("exit", handleShutdown); while (hasMore) { // Check if shutdown was requested @@ -409,8 +408,7 @@ export async function migrateUsers() { } // Remove signal handlers - process.off("SIGINT", handleShutdown); - process.off("SIGTERM", handleShutdown); + process.off("exit", handleShutdown); const migrationEnd = performance.now(); const migrationDurationMs = migrationEnd - migrationStart; diff --git a/src/components/animation/comet-card.tsx b/src/components/animation/comet-card.tsx index b07630287..79f813f42 100644 --- a/src/components/animation/comet-card.tsx +++ b/src/components/animation/comet-card.tsx @@ -69,28 +69,18 @@ export const CometCard = ({ {children} diff --git a/src/components/animation/spotlight.tsx b/src/components/animation/spotlight.tsx index 4a40351e5..bd5f0e925 100644 --- a/src/components/animation/spotlight.tsx +++ b/src/components/animation/spotlight.tsx @@ -33,8 +33,7 @@ export const Spotlight = ({
({ + DndContext: ({ children }: { children: React.ReactNode }) => <>{children}, + closestCenter: vi.fn(), + KeyboardSensor: vi.fn(), + PointerSensor: vi.fn(), + useSensor: vi.fn(() => ({})), + useSensors: vi.fn(() => []), +})); + +vi.mock("@dnd-kit/sortable", () => ({ + SortableContext: ({ children }: { children: React.ReactNode }) => <>{children}, + horizontalListSortingStrategy: vi.fn(), + sortableKeyboardCoordinates: vi.fn(), + useSortable: () => ({ + attributes: {}, + listeners: {}, + setNodeRef: vi.fn(), + transform: null, + transition: null, + isDragging: false, + }), +})); + +vi.mock("@dnd-kit/utilities", () => ({ + CSS: { Transform: { toString: () => "" } }, +})); + +// Mock framer motion +vi.mock("motion/react", () => ({ + motion: { + div: ({ children, ...props }: any) =>
{children}
, + }, +})); + +// Mock lingui +vi.mock("@lingui/react/macro", () => ({ + Trans: ({ children }: { children: React.ReactNode }) => <>{children}, +})); + +// Mock phosphor icons +vi.mock("@phosphor-icons/react", () => ({ + PencilSimpleIcon: ({ className }: any) => , + XIcon: ({ className }: any) => , +})); + +import { ChipInput } from "./chip-input"; + +describe("ChipInput", () => { + describe("rendering", () => { + it("renders the input field", () => { + render(); + expect(screen.getByRole("textbox")).toBeDefined(); + }); + + it("renders existing chips", () => { + render(); + expect(screen.getByText("React")).toBeDefined(); + expect(screen.getByText("TypeScript")).toBeDefined(); + expect(screen.getByText("Node")).toBeDefined(); + }); + + it("renders no chips when value is empty", () => { + render(); + + // Only the input area, no chip badges + expect(screen.queryByText("React")).toBeNull(); + }); + + it("shows description by default", () => { + render(); + expect(screen.getByText(/Enter/)).toBeDefined(); + }); + + it("hides description when hideDescription is true", () => { + render(); + expect(screen.queryByText(/Enter/)).toBeNull(); + }); + }); + + describe("adding chips via Enter key", () => { + it("adds a chip when Enter is pressed", () => { + const onChange = vi.fn(); + render(); + + const input = screen.getByRole("textbox"); + fireEvent.change(input, { target: { value: "React" } }); + fireEvent.keyDown(input, { key: "Enter" }); + + expect(onChange).toHaveBeenCalledWith(["React"]); + }); + + it("does not add empty chips", () => { + const onChange = vi.fn(); + render(); + + const input = screen.getByRole("textbox"); + fireEvent.change(input, { target: { value: " " } }); + fireEvent.keyDown(input, { key: "Enter" }); + + expect(onChange).not.toHaveBeenCalled(); + }); + + it("trims whitespace from chips", () => { + const onChange = vi.fn(); + render(); + + const input = screen.getByRole("textbox"); + fireEvent.change(input, { target: { value: " React " } }); + fireEvent.keyDown(input, { key: "Enter" }); + + expect(onChange).toHaveBeenCalledWith(["React"]); + }); + + it("prevents duplicate chips", () => { + const onChange = vi.fn(); + render(); + + const input = screen.getByRole("textbox"); + fireEvent.change(input, { target: { value: "React" } }); + fireEvent.keyDown(input, { key: "Enter" }); + + // Set is used, so duplicates are filtered — onChange should be called with same array + expect(onChange).toHaveBeenCalledWith(["React"]); + }); + }); + + describe("adding chips via comma", () => { + it("adds a chip when comma is typed in the input", () => { + const onChange = vi.fn(); + render(); + + const input = screen.getByRole("textbox"); + fireEvent.change(input, { target: { value: "React," } }); + + expect(onChange).toHaveBeenCalledWith(["React"]); + }); + + it("handles multiple comma-separated values", () => { + const onChange = vi.fn(); + render(); + + const input = screen.getByRole("textbox"); + fireEvent.change(input, { target: { value: "React,TypeScript," } }); + + // First call adds "React", second call adds "TypeScript" + expect(onChange).toHaveBeenCalled(); + }); + }); + + describe("removing chips", () => { + it("calls onChange without the removed chip when remove button is clicked", () => { + const onChange = vi.fn(); + render(); + + // Find all remove buttons + const removeButtons = screen.getAllByLabelText(/Remove/); + fireEvent.click(removeButtons[1]); // Remove "Vue" + + expect(onChange).toHaveBeenCalledWith(["React", "Angular"]); + }); + + it("removes the first chip correctly", () => { + const onChange = vi.fn(); + render(); + + const removeButtons = screen.getAllByLabelText(/Remove/); + fireEvent.click(removeButtons[0]); + + expect(onChange).toHaveBeenCalledWith(["B", "C"]); + }); + + it("removes the last chip correctly", () => { + const onChange = vi.fn(); + render(); + + const removeButtons = screen.getAllByLabelText(/Remove/); + fireEvent.click(removeButtons[2]); + + expect(onChange).toHaveBeenCalledWith(["A", "B"]); + }); + }); + + describe("editing chips", () => { + it("enters edit mode when edit button is clicked", () => { + render(); + + const editButtons = screen.getAllByLabelText(/Edit/); + fireEvent.click(editButtons[0]); + + const input = screen.getByRole("textbox"); + expect((input as HTMLInputElement).value).toBe("React"); + expect(input.getAttribute("aria-label")).toBe("Edit keyword"); + }); + + it("exits edit mode with Escape key", () => { + render(); + + const editButton = screen.getByLabelText("Edit React"); + fireEvent.click(editButton); + + const input = screen.getByRole("textbox"); + fireEvent.keyDown(input, { key: "Escape" }); + + expect((input as HTMLInputElement).value).toBe(""); + expect(input.getAttribute("aria-label")).toBe("Add keyword"); + }); + + it("saves edit when Enter is pressed", () => { + const onChange = vi.fn(); + render(); + + const editButton = screen.getByLabelText("Edit React"); + fireEvent.click(editButton); + + const input = screen.getByRole("textbox"); + fireEvent.change(input, { target: { value: "React.js" } }); + fireEvent.keyDown(input, { key: "Enter" }); + + expect(onChange).toHaveBeenCalledWith(["React.js"]); + }); + }); + + describe("input placeholder", () => { + it("shows 'Add a keyword...' by default", () => { + render(); + expect(screen.getByPlaceholderText("Add a keyword...")).toBeDefined(); + }); + + it("shows 'Editing keyword...' in edit mode", () => { + render(); + + const editButton = screen.getByLabelText("Edit Test"); + fireEvent.click(editButton); + + expect(screen.getByPlaceholderText("Editing keyword...")).toBeDefined(); + }); + }); + + describe("uncontrolled mode", () => { + it("works with defaultValue", () => { + render(); + expect(screen.getByText("Initial")).toBeDefined(); + }); + }); +}); diff --git a/src/components/input/chip-input.tsx b/src/components/input/chip-input.tsx index 033cb2fab..22ac6d17e 100644 --- a/src/components/input/chip-input.tsx +++ b/src/components/input/chip-input.tsx @@ -70,11 +70,10 @@ function ChipItem({ id, chip, index, isEditing, onEdit, onRemove }: ChipItemProp > {chip} + {result !== null && {String(result)}} +
+ ); +} + +// --------------------------------------------------------------------------- +// Tests +// --------------------------------------------------------------------------- + +describe("useConfirm", () => { + it("throws when used outside provider", () => { + expect(() => { + renderHook(() => useConfirm()); + }).toThrow("useConfirm must be used within a "); + }); + + it("returns a function when used inside provider", () => { + const { result } = renderHook(() => useConfirm(), { wrapper }); + expect(typeof result.current).toBe("function"); + }); + + it("confirm() returns a promise", () => { + const { result } = renderHook(() => useConfirm(), { wrapper }); + + let promise: Promise | undefined; + act(() => { + promise = result.current("Are you sure?"); + }); + + expect(promise).toBeInstanceOf(Promise); + }); + + it("opens dialog with title and description when confirm is called", async () => { + render( + + + , + ); + + await act(async () => { + screen.getByText("Trigger").click(); + }); + + expect(screen.getByText("Delete this?")).toBeDefined(); + expect(screen.getByText("This action cannot be undone.")).toBeDefined(); + expect(screen.getByText("Delete")).toBeDefined(); + expect(screen.getByText("Keep")).toBeDefined(); + }); + + it("resolves true when confirm button is clicked", async () => { + render( + + + , + ); + + await act(async () => { + screen.getByText("Trigger").click(); + }); + + await act(async () => { + screen.getByText("Delete").click(); + }); + + expect(screen.getByTestId("result").textContent).toBe("true"); + }); + + it("resolves false when cancel button is clicked", async () => { + render( + + + , + ); + + await act(async () => { + screen.getByText("Trigger").click(); + }); + + await act(async () => { + screen.getByText("Keep").click(); + }); + + expect(screen.getByTestId("result").textContent).toBe("false"); + }); +}); diff --git a/src/hooks/use-controlled-state.test.tsx b/src/hooks/use-controlled-state.test.tsx new file mode 100644 index 000000000..1e55f5f53 --- /dev/null +++ b/src/hooks/use-controlled-state.test.tsx @@ -0,0 +1,109 @@ +import { act, renderHook } from "@testing-library/react"; +import { describe, expect, it, vi } from "vite-plus/test"; + +import { useControlledState } from "./use-controlled-state"; + +describe("useControlledState", () => { + describe("uncontrolled mode (no value prop)", () => { + it("uses defaultValue as initial state", () => { + const { result } = renderHook(() => useControlledState({ defaultValue: "hello" })); + expect(result.current[0]).toBe("hello"); + }); + + it("updates internal state when setter is called", () => { + const { result } = renderHook(() => useControlledState({ defaultValue: 0 })); + + act(() => { + result.current[1](42); + }); + + expect(result.current[0]).toBe(42); + }); + + it("calls onChange when setter is called", () => { + const onChange = vi.fn(); + const { result } = renderHook(() => useControlledState({ defaultValue: "a", onChange })); + + act(() => { + result.current[1]("b"); + }); + + expect(onChange).toHaveBeenCalledWith("b"); + }); + + it("passes extra args to onChange", () => { + const onChange = vi.fn(); + const { result } = renderHook(() => useControlledState({ defaultValue: "a", onChange })); + + act(() => { + result.current[1]("b", 99); + }); + + expect(onChange).toHaveBeenCalledWith("b", 99); + }); + }); + + describe("controlled mode (value prop provided)", () => { + it("uses value prop as initial state", () => { + const { result } = renderHook(() => useControlledState({ value: "controlled" })); + expect(result.current[0]).toBe("controlled"); + }); + + it("syncs internal state when value prop changes", () => { + let value = "first"; + const { result, rerender } = renderHook(() => useControlledState({ value })); + + expect(result.current[0]).toBe("first"); + + value = "second"; + rerender(); + + expect(result.current[0]).toBe("second"); + }); + + it("still calls onChange when setter is called in controlled mode", () => { + const onChange = vi.fn(); + const { result } = renderHook(() => useControlledState({ value: "x", onChange })); + + act(() => { + result.current[1]("y"); + }); + + expect(onChange).toHaveBeenCalledWith("y"); + }); + }); + + describe("edge cases", () => { + it("handles undefined defaultValue", () => { + const { result } = renderHook(() => useControlledState({})); + expect(result.current[0]).toBeUndefined(); + }); + + it("handles boolean values", () => { + const { result } = renderHook(() => useControlledState({ defaultValue: false })); + expect(result.current[0]).toBe(false); + + act(() => { + result.current[1](true); + }); + + expect(result.current[0]).toBe(true); + }); + + it("handles object values", () => { + const obj = { key: "value" }; + const { result } = renderHook(() => useControlledState({ defaultValue: obj })); + expect(result.current[0]).toBe(obj); + }); + + it("works without onChange callback", () => { + const { result } = renderHook(() => useControlledState({ defaultValue: 5 })); + + act(() => { + result.current[1](10); + }); + + expect(result.current[0]).toBe(10); + }); + }); +}); diff --git a/src/hooks/use-mobile.test.tsx b/src/hooks/use-mobile.test.tsx new file mode 100644 index 000000000..0e0093801 --- /dev/null +++ b/src/hooks/use-mobile.test.tsx @@ -0,0 +1,104 @@ +import { act, renderHook } from "@testing-library/react"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vite-plus/test"; + +import { useIsMobile } from "./use-mobile"; + +// --------------------------------------------------------------------------- +// Mock window.matchMedia +// --------------------------------------------------------------------------- + +type MockMatchMedia = { + matches: boolean; + listeners: Array<(e: { matches: boolean }) => void>; + trigger: (matches: boolean) => void; +}; + +let mockMql: MockMatchMedia; + +beforeEach(() => { + mockMql = { + matches: false, + listeners: [], + trigger(matches: boolean) { + this.matches = matches; + for (const listener of this.listeners) { + listener({ matches }); + } + }, + }; + + vi.stubGlobal( + "matchMedia", + vi.fn(() => ({ + get matches() { + return mockMql.matches; + }, + addEventListener: (_event: string, cb: (e: { matches: boolean }) => void) => { + mockMql.listeners.push(cb); + }, + removeEventListener: (_event: string, cb: (e: { matches: boolean }) => void) => { + mockMql.listeners = mockMql.listeners.filter((l) => l !== cb); + }, + })), + ); +}); + +afterEach(() => { + vi.unstubAllGlobals(); +}); + +// --------------------------------------------------------------------------- +// Tests +// --------------------------------------------------------------------------- + +describe("useIsMobile", () => { + it("returns false on desktop-sized screens", () => { + mockMql.matches = false; + const { result } = renderHook(() => useIsMobile()); + expect(result.current).toBe(false); + }); + + it("returns true on mobile-sized screens", () => { + mockMql.matches = true; + const { result } = renderHook(() => useIsMobile()); + expect(result.current).toBe(true); + }); + + it("updates when screen size changes to mobile", () => { + mockMql.matches = false; + const { result } = renderHook(() => useIsMobile()); + expect(result.current).toBe(false); + + act(() => { + mockMql.trigger(true); + }); + + expect(result.current).toBe(true); + }); + + it("updates when screen size changes to desktop", () => { + mockMql.matches = true; + const { result } = renderHook(() => useIsMobile()); + expect(result.current).toBe(true); + + act(() => { + mockMql.trigger(false); + }); + + expect(result.current).toBe(false); + }); + + it("cleans up event listener on unmount", () => { + mockMql.matches = false; + const { unmount } = renderHook(() => useIsMobile()); + + expect(mockMql.listeners).toHaveLength(1); + unmount(); + expect(mockMql.listeners).toHaveLength(0); + }); + + it("uses 768px breakpoint (max-width: 767px)", () => { + renderHook(() => useIsMobile()); + expect(window.matchMedia).toHaveBeenCalledWith("(max-width: 767px)"); + }); +}); diff --git a/src/hooks/use-prompt.test.tsx b/src/hooks/use-prompt.test.tsx new file mode 100644 index 000000000..1a2804fa8 --- /dev/null +++ b/src/hooks/use-prompt.test.tsx @@ -0,0 +1,159 @@ +import { act, fireEvent, render, renderHook, screen } from "@testing-library/react"; +import React from "react"; +import { afterEach, describe, expect, it, vi } from "vite-plus/test"; + +vi.mock("@lingui/core/macro", () => ({ + t: (strings: TemplateStringsArray) => strings[0], +})); + +import { PromptDialogProvider, usePrompt } from "./use-prompt"; + +afterEach(() => { + document.body.innerHTML = ""; +}); + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +function wrapper({ children }: { children: React.ReactNode }) { + return {children}; +} + +function PromptTester() { + const prompt = usePrompt(); + const [result, setResult] = React.useState(undefined); + + return ( +
+ + {result !== undefined && {result === null ? "null" : result}} +
+ ); +} + +// --------------------------------------------------------------------------- +// Tests +// --------------------------------------------------------------------------- + +describe("usePrompt", () => { + it("throws when used outside provider", () => { + expect(() => { + renderHook(() => usePrompt()); + }).toThrow("usePrompt must be used within a "); + }); + + it("returns a function when used inside provider", () => { + const { result } = renderHook(() => usePrompt(), { wrapper }); + expect(typeof result.current).toBe("function"); + }); + + it("prompt() returns a promise", () => { + const { result } = renderHook(() => usePrompt(), { wrapper }); + + let promise: Promise | undefined; + act(() => { + promise = result.current("Enter value"); + }); + + expect(promise).toBeInstanceOf(Promise); + }); + + it("opens dialog with title, description, and default value", async () => { + render( + + + , + ); + + await act(async () => { + screen.getByText("Open Prompt").click(); + }); + + expect(screen.getByText("Enter name")).toBeDefined(); + expect(screen.getByText("Provide your full name")).toBeDefined(); + expect(screen.getByText("Submit")).toBeDefined(); + expect(screen.getByText("Skip")).toBeDefined(); + + // Check default value in input + const input = screen.getByDisplayValue("John"); + expect(input).toBeDefined(); + }); + + it("resolves with input value when confirm is clicked", async () => { + render( + + + , + ); + + await act(async () => { + screen.getByText("Open Prompt").click(); + }); + + // Change the input value + const input = screen.getByDisplayValue("John"); + await act(async () => { + fireEvent.change(input, { target: { value: "Jane Doe" } }); + }); + + await act(async () => { + screen.getByText("Submit").click(); + }); + + expect(screen.getByTestId("result").textContent).toBe("Jane Doe"); + }); + + it("resolves with null when cancel is clicked", async () => { + render( + + + , + ); + + await act(async () => { + screen.getByText("Open Prompt").click(); + }); + + await act(async () => { + screen.getByText("Skip").click(); + }); + + expect(screen.getByTestId("result").textContent).toBe("null"); + }); + + it("resolves with value when Enter key is pressed", async () => { + render( + + + , + ); + + await act(async () => { + screen.getByText("Open Prompt").click(); + }); + + const input = screen.getByDisplayValue("John"); + await act(async () => { + fireEvent.change(input, { target: { value: "Enter User" } }); + }); + + await act(async () => { + fireEvent.keyDown(input, { key: "Enter" }); + }); + + expect(screen.getByTestId("result").textContent).toBe("Enter User"); + }); +}); diff --git a/src/integrations/ai/store.test.ts b/src/integrations/ai/store.test.ts new file mode 100644 index 000000000..f929aa699 --- /dev/null +++ b/src/integrations/ai/store.test.ts @@ -0,0 +1,203 @@ +import { afterEach, describe, expect, it } from "vite-plus/test"; + +import { useAIStore } from "./store"; + +// --------------------------------------------------------------------------- +// Reset store between tests +// --------------------------------------------------------------------------- + +afterEach(() => { + useAIStore.getState().reset(); +}); + +// --------------------------------------------------------------------------- +// Initial state +// --------------------------------------------------------------------------- + +describe("AI Store — initial state", () => { + it("starts with default values", () => { + const state = useAIStore.getState(); + expect(state.enabled).toBe(false); + expect(state.provider).toBe("openai"); + expect(state.model).toBe(""); + expect(state.apiKey).toBe(""); + expect(state.baseURL).toBe(""); + expect(state.testStatus).toBe("unverified"); + }); +}); + +// --------------------------------------------------------------------------- +// set() +// --------------------------------------------------------------------------- + +describe("AI Store — set()", () => { + it("updates provider", () => { + useAIStore.getState().set((draft) => { + draft.provider = "anthropic"; + }); + expect(useAIStore.getState().provider).toBe("anthropic"); + }); + + it("updates model and apiKey", () => { + useAIStore.getState().set((draft) => { + draft.model = "gpt-4"; + draft.apiKey = "sk-test-key"; + }); + expect(useAIStore.getState().model).toBe("gpt-4"); + expect(useAIStore.getState().apiKey).toBe("sk-test-key"); + }); + + it("resets testStatus to unverified when provider changes", () => { + // First set testStatus to success + useAIStore.getState().set((draft) => { + draft.testStatus = "success"; + }); + // Now change provider — should reset + useAIStore.getState().set((draft) => { + draft.provider = "gemini"; + }); + expect(useAIStore.getState().testStatus).toBe("unverified"); + }); + + it("resets testStatus to unverified when model changes", () => { + useAIStore.getState().set((draft) => { + draft.testStatus = "success"; + }); + useAIStore.getState().set((draft) => { + draft.model = "new-model"; + }); + expect(useAIStore.getState().testStatus).toBe("unverified"); + }); + + it("resets testStatus to unverified when apiKey changes", () => { + useAIStore.getState().set((draft) => { + draft.testStatus = "success"; + }); + useAIStore.getState().set((draft) => { + draft.apiKey = "new-key"; + }); + expect(useAIStore.getState().testStatus).toBe("unverified"); + }); + + it("resets testStatus to unverified when baseURL changes", () => { + useAIStore.getState().set((draft) => { + draft.testStatus = "success"; + }); + useAIStore.getState().set((draft) => { + draft.baseURL = "https://new-url.com"; + }); + expect(useAIStore.getState().testStatus).toBe("unverified"); + }); + + it("disables when config changes", () => { + useAIStore.getState().set((draft) => { + draft.testStatus = "success"; + }); + useAIStore.getState().setEnabled(true); + expect(useAIStore.getState().enabled).toBe(true); + + // Change provider — should disable + useAIStore.getState().set((draft) => { + draft.provider = "ollama"; + }); + expect(useAIStore.getState().enabled).toBe(false); + }); + + it("does NOT reset testStatus when non-config fields change", () => { + useAIStore.getState().set((draft) => { + draft.testStatus = "success"; + }); + // Changing testStatus itself shouldn't trigger the reset logic + // (since provider/model/apiKey/baseURL didn't change) + expect(useAIStore.getState().testStatus).toBe("success"); + }); +}); + +// --------------------------------------------------------------------------- +// canEnable() +// --------------------------------------------------------------------------- + +describe("AI Store — canEnable()", () => { + it("returns false when testStatus is unverified", () => { + expect(useAIStore.getState().canEnable()).toBe(false); + }); + + it("returns false when testStatus is failure", () => { + useAIStore.getState().set((draft) => { + draft.testStatus = "failure"; + }); + expect(useAIStore.getState().canEnable()).toBe(false); + }); + + it("returns true when testStatus is success", () => { + useAIStore.getState().set((draft) => { + draft.testStatus = "success"; + }); + expect(useAIStore.getState().canEnable()).toBe(true); + }); +}); + +// --------------------------------------------------------------------------- +// setEnabled() +// --------------------------------------------------------------------------- + +describe("AI Store — setEnabled()", () => { + it("enables when testStatus is success", () => { + useAIStore.getState().set((draft) => { + draft.testStatus = "success"; + }); + useAIStore.getState().setEnabled(true); + expect(useAIStore.getState().enabled).toBe(true); + }); + + it("refuses to enable when testStatus is not success", () => { + useAIStore.getState().setEnabled(true); + expect(useAIStore.getState().enabled).toBe(false); + }); + + it("can disable regardless of testStatus", () => { + useAIStore.getState().set((draft) => { + draft.testStatus = "success"; + }); + useAIStore.getState().setEnabled(true); + expect(useAIStore.getState().enabled).toBe(true); + + useAIStore.getState().setEnabled(false); + expect(useAIStore.getState().enabled).toBe(false); + }); + + it("refuses to enable after failure", () => { + useAIStore.getState().set((draft) => { + draft.testStatus = "failure"; + }); + useAIStore.getState().setEnabled(true); + expect(useAIStore.getState().enabled).toBe(false); + }); +}); + +// --------------------------------------------------------------------------- +// reset() +// --------------------------------------------------------------------------- + +describe("AI Store — reset()", () => { + it("restores all state to initial values", () => { + useAIStore.getState().set((draft) => { + draft.provider = "anthropic"; + draft.model = "claude-3"; + draft.apiKey = "sk-key"; + draft.baseURL = "https://api.anthropic.com"; + draft.testStatus = "success"; + }); + useAIStore.getState().setEnabled(true); + + useAIStore.getState().reset(); + + const state = useAIStore.getState(); + expect(state.enabled).toBe(false); + expect(state.provider).toBe("openai"); + expect(state.model).toBe(""); + expect(state.apiKey).toBe(""); + expect(state.baseURL).toBe(""); + expect(state.testStatus).toBe("unverified"); + }); +}); diff --git a/src/integrations/auth/client.ts b/src/integrations/auth/client.ts index 139dcc610..e5e1a2e1d 100644 --- a/src/integrations/auth/client.ts +++ b/src/integrations/auth/client.ts @@ -2,7 +2,13 @@ import { apiKeyClient } from "@better-auth/api-key/client"; import { dashClient } from "@better-auth/infra/client"; import { oauthProviderClient } from "@better-auth/oauth-provider/client"; import { oauthProviderResourceClient } from "@better-auth/oauth-provider/resource-client"; -import { genericOAuthClient, inferAdditionalFields, twoFactorClient, usernameClient } from "better-auth/client/plugins"; +import { + adminClient, + genericOAuthClient, + inferAdditionalFields, + twoFactorClient, + usernameClient, +} from "better-auth/client/plugins"; import { createAuthClient } from "better-auth/react"; import type { auth } from "./config"; @@ -11,6 +17,7 @@ const getAuthClient = () => { return createAuthClient({ plugins: [ dashClient(), + adminClient(), apiKeyClient(), usernameClient(), twoFactorClient({ diff --git a/src/integrations/auth/config.ts b/src/integrations/auth/config.ts index 793ca2364..f5b61d288 100644 --- a/src/integrations/auth/config.ts +++ b/src/integrations/auth/config.ts @@ -1,4 +1,3 @@ -import type { GenericOAuthConfig } from "better-auth/plugins"; import type { JWTPayload } from "jose"; import { apiKey } from "@better-auth/api-key"; @@ -7,7 +6,7 @@ import { dash } from "@better-auth/infra"; import { oauthProvider } from "@better-auth/oauth-provider"; import { BetterAuthError, betterAuth } from "better-auth"; import { verifyAccessToken } from "better-auth/oauth2"; -import { jwt, openAPI } from "better-auth/plugins"; +import { admin, jwt, openAPI, type GenericOAuthConfig } from "better-auth/plugins"; import { genericOAuth } from "better-auth/plugins/generic-oauth"; import { twoFactor } from "better-auth/plugins/two-factor"; import { username } from "better-auth/plugins/username"; @@ -124,7 +123,7 @@ const getAuthConfig = () => { emailAndPassword: { enabled: !env.FLAG_DISABLE_EMAIL_AUTH, autoSignIn: true, - minPasswordLength: 6, + minPasswordLength: 8, maxPasswordLength: 64, requireEmailVerification: false, disableSignUp: env.FLAG_DISABLE_SIGNUPS || env.FLAG_DISABLE_EMAIL_AUTH, @@ -175,7 +174,7 @@ const getAuthConfig = () => { account: { accountLinking: { enabled: true, - trustedProviders: ["google", "github"], + trustedProviders: ["google", "github", "linkedin"], }, }, @@ -249,10 +248,38 @@ const getAuthConfig = () => { }; }, }, + + linkedin: { + enabled: !!env.LINKEDIN_CLIENT_ID && !!env.LINKEDIN_CLIENT_SECRET, + disableSignUp: env.FLAG_DISABLE_SIGNUPS, + clientId: env.LINKEDIN_CLIENT_ID!, + clientSecret: env.LINKEDIN_CLIENT_SECRET!, + mapProfileToUser: async (profile) => { + if (!profile.email) { + throw new BetterAuthError( + "LinkedIn provider did not return an email address. This is required for user creation.", + { cause: "EMAIL_REQUIRED" }, + ); + } + + const username = profile.email.split("@")[0]; + const name = profile.name ?? username; + + return { + name, + email: profile.email, + image: profile.picture, + username, + displayUsername: username, + emailVerified: true, + }; + }, + }, }, plugins: [ jwt(), + admin(), openAPI(), genericOAuth({ config: authConfigs }), twoFactor({ issuer: "Reactive Resume" }), diff --git a/src/integrations/auth/types.ts b/src/integrations/auth/types.ts index 9ff160f77..af7c86bbd 100644 --- a/src/integrations/auth/types.ts +++ b/src/integrations/auth/types.ts @@ -7,6 +7,6 @@ export type AuthSession = { user: typeof auth.$Infer.Session.user; }; -const authProviderSchema = z.enum(["credential", "google", "github", "custom"]); +const authProviderSchema = z.enum(["credential", "google", "github", "linkedin", "custom"]); export type AuthProvider = z.infer; diff --git a/src/integrations/drizzle/schema.ts b/src/integrations/drizzle/schema.ts index f569c709f..612e988c1 100644 --- a/src/integrations/drizzle/schema.ts +++ b/src/integrations/drizzle/schema.ts @@ -20,6 +20,10 @@ export const user = pg.pgTable( displayUsername: pg.text("display_username").notNull().unique(), twoFactorEnabled: pg.boolean("two_factor_enabled").notNull().default(false), lastActiveAt: pg.timestamp("last_active_at", { withTimezone: true }), + role: pg.text("role").default("user"), + banned: pg.boolean("banned").default(false), + banReason: pg.text("ban_reason"), + banExpires: pg.timestamp("ban_expires", { precision: 6, withTimezone: true }), createdAt: pg.timestamp("created_at", { withTimezone: true }).notNull().defaultNow(), updatedAt: pg .timestamp("updated_at", { withTimezone: true }) @@ -41,6 +45,7 @@ export const session = pg.pgTable( token: pg.text("token").notNull().unique(), ipAddress: pg.text("ip_address"), userAgent: pg.text("user_agent"), + impersonatedBy: pg.text("impersonated_by"), userId: pg .uuid("user_id") .notNull() diff --git a/src/integrations/import/json-resume.test.ts b/src/integrations/import/json-resume.test.ts new file mode 100644 index 000000000..2baadcecb --- /dev/null +++ b/src/integrations/import/json-resume.test.ts @@ -0,0 +1,327 @@ +import { describe, expect, it } from "vite-plus/test"; + +import { resumeDataSchema } from "@/schema/resume/data"; + +import { JSONResumeImporter } from "./json-resume"; + +const importer = new JSONResumeImporter(); + +function makeMinimalJsonResume(overrides?: Record) { + return { + basics: { + name: "John Doe", + label: "Software Engineer", + email: "john@example.com", + phone: "+1-555-0100", + summary: "A passionate developer.", + location: { city: "San Francisco", region: "CA", countryCode: "US" }, + url: "https://johndoe.com", + profiles: [{ network: "GitHub", username: "johndoe", url: "https://github.com/johndoe" }], + }, + work: [ + { + name: "Acme Corp", + position: "Senior Developer", + startDate: "2020-01", + endDate: "2024-06", + summary: "Led a team of 5.", + highlights: ["Built CI/CD pipeline", "Reduced build time by 50%"], + }, + ], + education: [ + { + institution: "MIT", + studyType: "Bachelor", + area: "Computer Science", + score: "3.9", + startDate: "2016-09", + endDate: "2020-05", + }, + ], + skills: [{ name: "TypeScript", level: "Advanced", keywords: ["React", "Node.js"] }], + languages: [{ language: "English", fluency: "Native" }], + ...overrides, + }; +} + +describe("JSONResumeImporter", () => { + describe("parse", () => { + it("parses a valid JSON Resume and produces valid ResumeData", () => { + const result = importer.parse(JSON.stringify(makeMinimalJsonResume())); + expect(resumeDataSchema.safeParse(result).success).toBe(true); + }); + + it("throws on invalid JSON", () => { + expect(() => importer.parse("not json")).toThrow(); + }); + }); + + describe("convert - basics", () => { + it("maps name and headline", () => { + const result = importer.parse(JSON.stringify(makeMinimalJsonResume())); + expect(result.basics.name).toBe("John Doe"); + expect(result.basics.headline).toBe("Software Engineer"); + }); + + it("maps email and phone", () => { + const result = importer.parse(JSON.stringify(makeMinimalJsonResume())); + expect(result.basics.email).toBe("john@example.com"); + expect(result.basics.phone).toBe("+1-555-0100"); + }); + + it("formats location from object to string", () => { + const result = importer.parse(JSON.stringify(makeMinimalJsonResume())); + expect(result.basics.location).toBe("San Francisco, CA, US"); + }); + + it("maps website URL", () => { + const result = importer.parse(JSON.stringify(makeMinimalJsonResume())); + expect(result.basics.website.url).toBe("https://johndoe.com"); + }); + }); + + describe("convert - summary", () => { + it("wraps summary in HTML", () => { + const result = importer.parse(JSON.stringify(makeMinimalJsonResume())); + expect(result.summary.content).toBe("

A passionate developer.

"); + expect(result.summary.hidden).toBe(false); + }); + + it("handles missing summary", () => { + const data = makeMinimalJsonResume(); + delete (data.basics as Record).summary; + const result = importer.parse(JSON.stringify(data)); + expect(result.summary.content).toBe(""); + }); + }); + + describe("convert - experience", () => { + it("maps work to experience items", () => { + const result = importer.parse(JSON.stringify(makeMinimalJsonResume())); + expect(result.sections.experience.items).toHaveLength(1); + const exp = result.sections.experience.items[0]; + expect(exp.company).toBe("Acme Corp"); + expect(exp.position).toBe("Senior Developer"); + expect(exp.period).toBe("January 2020 - June 2024"); + }); + + it("converts summary+highlights to HTML description", () => { + const result = importer.parse(JSON.stringify(makeMinimalJsonResume())); + const exp = result.sections.experience.items[0]; + expect(exp.description).toContain("

Led a team of 5.

"); + expect(exp.description).toContain("
  • Built CI/CD pipeline
  • "); + }); + + it("filters out entries without name or position", () => { + const data = makeMinimalJsonResume({ work: [{ location: "Nowhere" }] }); + const result = importer.parse(JSON.stringify(data)); + expect(result.sections.experience.items).toHaveLength(0); + }); + }); + + describe("convert - education", () => { + it("maps education items", () => { + const result = importer.parse(JSON.stringify(makeMinimalJsonResume())); + const edu = result.sections.education.items[0]; + expect(edu.school).toBe("MIT"); + expect(edu.degree).toBe("Bachelor in Computer Science"); + expect(edu.grade).toBe("3.9"); + }); + }); + + describe("convert - skills", () => { + it("maps skills with level parsing", () => { + const result = importer.parse(JSON.stringify(makeMinimalJsonResume())); + const skill = result.sections.skills.items[0]; + expect(skill.name).toBe("TypeScript"); + expect(skill.level).toBe(4); // "Advanced" maps to 4 + expect(skill.keywords).toEqual(["React", "Node.js"]); + }); + }); + + describe("convert - languages", () => { + it("maps languages with fluency level", () => { + const result = importer.parse(JSON.stringify(makeMinimalJsonResume())); + const lang = result.sections.languages.items[0]; + expect(lang.language).toBe("English"); + expect(lang.level).toBe(5); // "Native" maps to 5 + }); + }); + + describe("convert - profiles", () => { + it("maps profiles with network icons", () => { + const result = importer.parse(JSON.stringify(makeMinimalJsonResume())); + const profile = result.sections.profiles.items[0]; + expect(profile.network).toBe("GitHub"); + expect(profile.icon).toBe("github-logo"); + expect(profile.username).toBe("johndoe"); + }); + }); + + describe("convert - awards", () => { + it("maps awards with formatted dates", () => { + const data = makeMinimalJsonResume({ + awards: [{ title: "Best Paper", awarder: "IEEE", date: "2023-06-15", summary: "Great work" }], + }); + const result = importer.parse(JSON.stringify(data)); + const award = result.sections.awards.items[0]; + expect(award.title).toBe("Best Paper"); + expect(award.awarder).toBe("IEEE"); + expect(award.date).toBe("June 15, 2023"); + expect(award.description).toBe("

    Great work

    "); + }); + }); + + describe("convert - volunteer", () => { + it("maps volunteer items with period and description", () => { + const data = makeMinimalJsonResume({ + volunteer: [ + { + organization: "Red Cross", + position: "Volunteer", + startDate: "2022-01", + endDate: "2023-06", + summary: "Helped with logistics", + highlights: ["Organized events"], + url: "https://redcross.org", + }, + ], + }); + const result = importer.parse(JSON.stringify(data)); + const vol = result.sections.volunteer.items[0]; + expect(vol.organization).toBe("Red Cross"); + expect(vol.period).toBe("January 2022 - June 2023"); + expect(vol.description).toContain("Helped with logistics"); + expect(vol.description).toContain("Organized events"); + }); + + it("filters out volunteer items without organization", () => { + const data = makeMinimalJsonResume({ + volunteer: [ + { organization: "Valid Org", position: "Vol" }, + { position: "No Org" }, // missing organization + ], + }); + const result = importer.parse(JSON.stringify(data)); + expect(result.sections.volunteer.items).toHaveLength(1); + }); + }); + + describe("convert - references", () => { + it("maps references with name and reference text", () => { + const data = makeMinimalJsonResume({ + references: [{ name: "John Manager", reference: "Great developer and team player" }], + }); + const result = importer.parse(JSON.stringify(data)); + const ref = result.sections.references.items[0]; + expect(ref.name).toBe("John Manager"); + expect(ref.description).toBe("

    Great developer and team player

    "); + }); + + it("handles references without reference text", () => { + const data = makeMinimalJsonResume({ + references: [{ name: "Jane CTO" }], + }); + const result = importer.parse(JSON.stringify(data)); + expect(result.sections.references.items[0].description).toBe(""); + }); + + it("filters references without name or reference", () => { + const data = makeMinimalJsonResume({ + references: [ + { name: "Valid" }, + {}, // no name or reference + ], + }); + const result = importer.parse(JSON.stringify(data)); + expect(result.sections.references.items).toHaveLength(1); + }); + }); + + describe("convert - publications", () => { + it("maps publications with formatted date", () => { + const data = makeMinimalJsonResume({ + publications: [ + { + name: "My Paper", + publisher: "IEEE", + releaseDate: "2024-03-15", + summary: "Research findings", + url: "https://doi.org/paper", + }, + ], + }); + const result = importer.parse(JSON.stringify(data)); + const pub = result.sections.publications.items[0]; + expect(pub.title).toBe("My Paper"); + expect(pub.publisher).toBe("IEEE"); + expect(pub.date).toBe("March 15, 2024"); + }); + }); + + describe("convert - certifications", () => { + it("maps certificates with issuer and date", () => { + const data = makeMinimalJsonResume({ + certificates: [{ name: "AWS Certified", issuer: "Amazon", date: "2023-06" }], + }); + const result = importer.parse(JSON.stringify(data)); + const cert = result.sections.certifications.items[0]; + expect(cert.title).toBe("AWS Certified"); + expect(cert.issuer).toBe("Amazon"); + expect(cert.date).toBe("June 2023"); + }); + }); + + describe("convert - interests", () => { + it("maps interests with keywords", () => { + const data = makeMinimalJsonResume({ + interests: [{ name: "Gaming", keywords: ["RPG", "Strategy"] }], + }); + const result = importer.parse(JSON.stringify(data)); + const interest = result.sections.interests.items[0]; + expect(interest.name).toBe("Gaming"); + expect(interest.keywords).toEqual(["RPG", "Strategy"]); + }); + }); + + describe("convert - empty sections", () => { + it("handles resume with no optional sections", () => { + const minimal = { basics: { name: "Jane" } }; + const result = importer.parse(JSON.stringify(minimal)); + expect(resumeDataSchema.safeParse(result).success).toBe(true); + expect(result.basics.name).toBe("Jane"); + }); + }); + + describe("error handling", () => { + it("throws on invalid JSON", () => { + expect(() => importer.parse("not json")).toThrow(); + }); + + it("handles missing basics.location gracefully", () => { + const data = { basics: { name: "Jane" } }; + const result = importer.parse(JSON.stringify(data)); + expect(result.basics.location).toBe(""); + }); + + it("allows work items with position but no name (falls back to position)", () => { + const data = makeMinimalJsonResume(); + // Work item with position but no name — filter passes because position is truthy + // The importer maps name -> company, so company will be empty + // This is a known limitation: the filter checks (name || position) but company requires min(1) + // The resumeDataSchema.parse() will reject it + (data as any).work.push({ name: "", position: "Ghost Dev", startDate: "2020-01" }); + expect(() => importer.parse(JSON.stringify(data))).toThrow(); + }); + + it("filters profiles without network name", () => { + const data = makeMinimalJsonResume(); + (data as any).basics.profiles = [ + { network: "GitHub", username: "jane" }, + { username: "no-network" }, // no network + ]; + const result = importer.parse(JSON.stringify(data)); + expect(result.sections.profiles.items).toHaveLength(1); + }); + }); +}); diff --git a/src/integrations/import/json-resume.tsx b/src/integrations/import/json-resume.tsx index 2334aadef..81706f97a 100644 --- a/src/integrations/import/json-resume.tsx +++ b/src/integrations/import/json-resume.tsx @@ -1,9 +1,12 @@ import { flattenError, ZodError, z } from "zod"; -import type { IconName } from "@/schema/icons"; - import { defaultResumeData, type ResumeData, resumeDataSchema } from "@/schema/resume/data"; +import { formatPeriod, formatSingleDate } from "@/utils/date"; +import { arrayToHtmlList, toHtmlDescription } from "@/utils/html"; +import { parseLevel } from "@/utils/level"; +import { getNetworkIcon } from "@/utils/network-icons"; import { generateId } from "@/utils/string"; +import { createUrl } from "@/utils/url"; // Custom ISO 8601 date pattern that allows partial dates (year only, year-month, or full date) const iso8601 = z @@ -153,138 +156,6 @@ const jsonResumeSchema = z.looseObject({ type JSONResume = z.infer; -// Helper function to format date period from start and end dates -function formatPeriod(startDate?: string, endDate?: string): string { - if (!startDate && !endDate) return ""; - if (!startDate) return endDate || ""; - if (!endDate) return `${startDate} - Present`; - - // Format dates to be more readable - const formatDate = (date: string): string => { - // Handle YYYY-MM-DD, YYYY-MM, or YYYY formats - const parts = date.split("-"); - - if (parts.length === 3) { - // YYYY-MM-DD - const [year, month] = parts; - const monthNames = [ - "January", - "February", - "March", - "April", - "May", - "June", - "July", - "August", - "September", - "October", - "November", - "December", - ]; - return `${monthNames[parseInt(month, 10) - 1]} ${year}`; - } - - if (parts.length === 2) { - // YYYY-MM - const [year, month] = parts; - const monthNames = [ - "January", - "February", - "March", - "April", - "May", - "June", - "July", - "August", - "September", - "October", - "November", - "December", - ]; - return `${monthNames[parseInt(month, 10) - 1]} ${year}`; - } - - // YYYY - return date; - }; - - return `${formatDate(startDate)} - ${formatDate(endDate)}`; -} - -// Helper function to format a single date -function formatSingleDate(date?: string): string { - if (!date) return ""; - - // Format dates to be more readable - const parts = date.split("-"); - - if (parts.length === 3) { - // YYYY-MM-DD - const [year, month, day] = parts; - const monthNames = [ - "January", - "February", - "March", - "April", - "May", - "June", - "July", - "August", - "September", - "October", - "November", - "December", - ]; - return `${monthNames[parseInt(month, 10) - 1]} ${day}, ${year}`; - } - if (parts.length === 2) { - // YYYY-MM - const [year, month] = parts; - const monthNames = [ - "January", - "February", - "March", - "April", - "May", - "June", - "July", - "August", - "September", - "October", - "November", - "December", - ]; - return `${monthNames[parseInt(month, 10) - 1]} ${year}`; - } - // YYYY - return date; -} - -// Helper function to convert text and highlights to HTML -function toHtmlDescription(summary?: string, highlights?: string[]): string { - const parts: string[] = []; - - if (summary) { - parts.push(`

    ${summary}

    `); - } - - if (highlights && highlights.length > 0) { - parts.push("
      "); - for (const highlight of highlights) { - parts.push(`
    • ${highlight}
    • `); - } - parts.push("
    "); - } - - return parts.join(""); -} - -// Helper function to convert array to HTML list -function arrayToHtmlList(items?: string[]): string { - if (!items || items.length === 0) return ""; - return `
      ${items.map((item) => `
    • ${item}
    • `).join("")}
    `; -} - // Helper function to format location object to string function formatLocation(location?: { address?: string; @@ -303,62 +174,6 @@ function formatLocation(location?: { return parts.join(", "); } -// Helper function to map network name to icon -function getNetworkIcon(network?: string): IconName { - if (!network) return "star"; - - const networkLower = network.toLowerCase(); - if (networkLower.includes("github")) return "github-logo"; - if (networkLower.includes("linkedin")) return "linkedin-logo"; - if (networkLower.includes("twitter") || networkLower.includes("x.com")) return "twitter-logo"; - if (networkLower.includes("facebook")) return "facebook-logo"; - if (networkLower.includes("instagram")) return "instagram-logo"; - if (networkLower.includes("youtube")) return "youtube-logo"; - if (networkLower.includes("stackoverflow") || networkLower.includes("stack-overflow")) return "stack-overflow-logo"; - if (networkLower.includes("medium")) return "medium-logo"; - if (networkLower.includes("dev.to") || networkLower.includes("devto")) return "code"; - if (networkLower.includes("dribbble")) return "dribbble-logo"; - if (networkLower.includes("behance")) return "behance-logo"; - if (networkLower.includes("gitlab")) return "git-branch"; - if (networkLower.includes("bitbucket")) return "code"; - if (networkLower.includes("codepen")) return "code"; - - return "star"; -} - -// Helper function to parse skill/language level to number (0-5) -function parseLevel(level?: string): number { - if (!level) return 0; - - const levelLower = level.toLowerCase(); - // Try to parse numeric values - const numeric = parseInt(levelLower, 10); - if (!Number.isNaN(numeric) && numeric >= 0 && numeric <= 5) return numeric; - - // Map text levels to numbers - if (levelLower.includes("native") || levelLower.includes("expert") || levelLower.includes("master")) return 5; - if (levelLower.includes("fluent") || levelLower.includes("advanced") || levelLower.includes("proficient")) return 4; - if (levelLower.includes("intermediate") || levelLower.includes("conversational")) return 3; - if (levelLower.includes("beginner") || levelLower.includes("basic") || levelLower.includes("elementary")) return 2; - if (levelLower.includes("novice")) return 1; - - // CEFR levels - if (levelLower.includes("c2")) return 5; - if (levelLower.includes("c1")) return 4; - if (levelLower.includes("b2")) return 3; - if (levelLower.includes("b1")) return 2; - if (levelLower.includes("a2")) return 1; - if (levelLower.includes("a1")) return 1; - - return 0; -} - -// Helper function to create URL object -function createUrl(url?: string, label?: string): { url: string; label: string } { - if (!url) return { url: "", label: "" }; - return { url, label: label || url }; -} - export class JSONResumeImporter { convert(jsonResume: JSONResume): ResumeData { const result: ResumeData = { diff --git a/src/integrations/import/reactive-resume-json.test.ts b/src/integrations/import/reactive-resume-json.test.ts new file mode 100644 index 000000000..6a3a6e80f --- /dev/null +++ b/src/integrations/import/reactive-resume-json.test.ts @@ -0,0 +1,62 @@ +import { describe, expect, it } from "vite-plus/test"; + +import { defaultResumeData, resumeDataSchema } from "@/schema/resume/data"; + +import { ReactiveResumeJSONImporter } from "./reactive-resume-json"; + +const importer = new ReactiveResumeJSONImporter(); + +describe("ReactiveResumeJSONImporter", () => { + it("parses valid ResumeData JSON", () => { + const result = importer.parse(JSON.stringify(defaultResumeData)); + expect(resumeDataSchema.safeParse(result).success).toBe(true); + }); + + it("throws on invalid JSON", () => { + expect(() => importer.parse("not json")).toThrow(); + }); + + it("throws on data that doesn't match schema", () => { + expect(() => importer.parse(JSON.stringify({ invalid: true }))).toThrow(); + }); + + it("normalizes missing layout sections", () => { + const data = { + ...defaultResumeData, + metadata: { + ...defaultResumeData.metadata, + layout: { + ...defaultResumeData.metadata.layout, + pages: [{ fullWidth: false, main: ["experience"], sidebar: [] }], + }, + }, + }; + const result = importer.parse(JSON.stringify(data)); + // Should add missing built-in section IDs to main + const allSectionIds = result.metadata.layout.pages.flatMap((p) => [...p.main, ...p.sidebar]); + expect(allSectionIds).toContain("experience"); + expect(allSectionIds).toContain("education"); + expect(allSectionIds).toContain("skills"); + }); + + it("handles empty layout pages by creating default page", () => { + const data = { + ...defaultResumeData, + metadata: { + ...defaultResumeData.metadata, + layout: { + ...defaultResumeData.metadata.layout, + pages: [], + }, + }, + }; + const result = importer.parse(JSON.stringify(data)); + expect(result.metadata.layout.pages).toHaveLength(1); + expect(result.metadata.layout.pages[0].main.length).toBeGreaterThan(0); + }); + + it("preserves complete layout without modification", () => { + const result = importer.parse(JSON.stringify(defaultResumeData)); + expect(result.metadata.layout).toEqual(defaultResumeData.metadata.layout); + }); +}); diff --git a/src/integrations/import/reactive-resume-v4-json.test.ts b/src/integrations/import/reactive-resume-v4-json.test.ts new file mode 100644 index 000000000..2e72c0aac --- /dev/null +++ b/src/integrations/import/reactive-resume-v4-json.test.ts @@ -0,0 +1,808 @@ +import { beforeEach, describe, expect, it, vi } from "vite-plus/test"; + +import type { SectionItem } from "@/schema/resume/data"; + +import { resumeDataSchema } from "@/schema/resume/data"; + +import { ReactiveResumeV4JSONImporter } from "./reactive-resume-v4-json"; + +// Mock generateId for deterministic output +let idCounter = 0; +vi.mock("@/utils/string", () => ({ + generateId: () => `mock-id-${++idCounter}`, + slugify: (str: string) => str.toLowerCase().replace(/\s+/g, "-"), + getInitials: (str: string) => + str + .split(" ") + .map((s) => s[0]) + .join(""), + toUsername: (str: string) => str.toLowerCase().replace(/\s+/g, ""), + stripHtml: (str: string) => str.replace(/<[^>]*>/g, ""), +})); + +beforeEach(() => { + idCounter = 0; +}); + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +const importer = new ReactiveResumeV4JSONImporter(); + +function makeMinimalV4Resume(overrides?: Record) { + return { + basics: { + name: "Jane Doe", + headline: "Developer", + email: "jane@example.com", + phone: "+1234567890", + location: "New York, NY", + url: { label: "Website", href: "https://jane.dev" }, + customFields: [], + picture: { + url: "https://example.com/photo.jpg", + size: 80, + aspectRatio: 1, + borderRadius: 10, + effects: { hidden: false, border: true, grayscale: false }, + }, + }, + sections: { + summary: { + name: "Summary", + columns: 1, + separateLinks: false, + visible: true, + id: "summary", + content: "

    A developer

    ", + }, + awards: { name: "Awards", columns: 1, separateLinks: false, visible: true, id: "awards", items: [] }, + certifications: { + name: "Certifications", + columns: 1, + separateLinks: false, + visible: true, + id: "certifications", + items: [], + }, + education: { name: "Education", columns: 1, separateLinks: false, visible: true, id: "education", items: [] }, + experience: { name: "Experience", columns: 1, separateLinks: false, visible: true, id: "experience", items: [] }, + volunteer: { name: "Volunteer", columns: 1, separateLinks: false, visible: true, id: "volunteer", items: [] }, + interests: { name: "Interests", columns: 1, separateLinks: false, visible: true, id: "interests", items: [] }, + languages: { name: "Languages", columns: 1, separateLinks: false, visible: true, id: "languages", items: [] }, + profiles: { name: "Profiles", columns: 1, separateLinks: false, visible: true, id: "profiles", items: [] }, + projects: { name: "Projects", columns: 1, separateLinks: false, visible: true, id: "projects", items: [] }, + publications: { + name: "Publications", + columns: 1, + separateLinks: false, + visible: true, + id: "publications", + items: [], + }, + references: { name: "References", columns: 1, separateLinks: false, visible: true, id: "references", items: [] }, + skills: { name: "Skills", columns: 1, separateLinks: false, visible: true, id: "skills", items: [] }, + }, + metadata: { + template: "onyx", + layout: [ + [ + ["experience", "education"], + ["skills", "languages"], + ], + ], + css: { value: "", visible: false }, + page: { margin: 14, format: "a4" as const, options: { breakLine: true, pageNumbers: true } }, + theme: { background: "#ffffff", text: "#000000", primary: "#dc2626" }, + typography: { + font: { family: "IBM Plex Serif", subset: "latin", variants: ["regular"], size: 14.67 }, + lineHeight: 1.5, + hideIcons: false, + underlineLinks: true, + }, + notes: "", + }, + ...overrides, + }; +} + +// --------------------------------------------------------------------------- +// Basic parsing +// --------------------------------------------------------------------------- + +describe("ReactiveResumeV4JSONImporter", () => { + describe("basic parsing", () => { + it("parses a minimal V4 resume and produces valid ResumeData", () => { + const result = importer.parse(JSON.stringify(makeMinimalV4Resume())); + expect(resumeDataSchema.safeParse(result).success).toBe(true); + }); + + it("maps basics fields correctly", () => { + const result = importer.parse(JSON.stringify(makeMinimalV4Resume())); + expect(result.basics.name).toBe("Jane Doe"); + expect(result.basics.headline).toBe("Developer"); + expect(result.basics.email).toBe("jane@example.com"); + expect(result.basics.phone).toBe("+1234567890"); + expect(result.basics.location).toBe("New York, NY"); + expect(result.basics.website.url).toBe("https://jane.dev"); + expect(result.basics.website.label).toBe("Website"); + }); + + it("maps picture fields correctly", () => { + const result = importer.parse(JSON.stringify(makeMinimalV4Resume())); + expect(result.picture.url).toBe("https://example.com/photo.jpg"); + expect(result.picture.size).toBe(80); + expect(result.picture.aspectRatio).toBe(1); + expect(result.picture.borderRadius).toBe(10); + expect(result.picture.hidden).toBe(false); + expect(result.picture.borderWidth).toBe(1); // border effect was true + }); + + it("maps summary correctly", () => { + const result = importer.parse(JSON.stringify(makeMinimalV4Resume())); + expect(result.summary.title).toBe("Summary"); + expect(result.summary.content).toBe("

    A developer

    "); + expect(result.summary.hidden).toBe(false); + }); + }); + + // --------------------------------------------------------------------------- + // Section items + // --------------------------------------------------------------------------- + + describe("section items", () => { + it("transforms experience items", () => { + const v4 = makeMinimalV4Resume(); + (v4.sections as any).experience.items = [ + { + id: "exp-1", + visible: true, + company: "Acme Corp", + position: "Engineer", + location: "NYC", + date: "2020 - 2022", + summary: "

    Built things

    ", + url: { label: "Acme", href: "https://acme.com" }, + }, + ]; + + const result = importer.parse(JSON.stringify(v4)); + expect(result.sections.experience.items).toHaveLength(1); + const exp = result.sections.experience.items[0]; + expect(exp.company).toBe("Acme Corp"); + expect(exp.position).toBe("Engineer"); + expect(exp.location).toBe("NYC"); + expect(exp.period).toBe("2020 - 2022"); + expect(exp.description).toBe("

    Built things

    "); + expect(exp.website.url).toBe("https://acme.com"); + }); + + it("transforms education items", () => { + const v4 = makeMinimalV4Resume(); + (v4.sections as any).education.items = [ + { + id: "edu-1", + visible: true, + institution: "MIT", + studyType: "BSc", + area: "Computer Science", + score: "4.0", + date: "2016 - 2020", + summary: "

    Graduated

    ", + url: { label: "MIT", href: "https://mit.edu" }, + }, + ]; + + const result = importer.parse(JSON.stringify(v4)); + const edu = result.sections.education.items[0]; + expect(edu.school).toBe("MIT"); + expect(edu.degree).toBe("BSc"); + expect(edu.area).toBe("Computer Science"); + expect(edu.grade).toBe("4.0"); + expect(edu.period).toBe("2016 - 2020"); + }); + + it("transforms skill items with level clamping", () => { + const v4 = makeMinimalV4Resume(); + (v4.sections as any).skills.items = [ + { id: "s1", visible: true, name: "TypeScript", description: "Advanced", level: 4, keywords: ["frontend"] }, + { id: "s2", visible: true, name: "Rust", description: "Beginner", level: 10, keywords: [] }, // level > 5 + ]; + + const result = importer.parse(JSON.stringify(v4)); + expect(result.sections.skills.items).toHaveLength(2); + expect(result.sections.skills.items[0].name).toBe("TypeScript"); + expect(result.sections.skills.items[0].proficiency).toBe("Advanced"); + expect(result.sections.skills.items[0].level).toBe(4); + expect(result.sections.skills.items[1].level).toBe(5); // clamped to 5 + }); + + it("transforms language items", () => { + const v4 = makeMinimalV4Resume(); + (v4.sections as any).languages.items = [ + { id: "l1", visible: true, name: "English", description: "Native", level: 5 }, + ]; + + const result = importer.parse(JSON.stringify(v4)); + expect(result.sections.languages.items[0].language).toBe("English"); + expect(result.sections.languages.items[0].fluency).toBe("Native"); + }); + + it("transforms profile items", () => { + const v4 = makeMinimalV4Resume(); + (v4.sections as any).profiles.items = [ + { + id: "p1", + visible: true, + network: "GitHub", + username: "janedoe", + icon: "github", + url: { label: "GitHub", href: "https://github.com/janedoe" }, + }, + ]; + + const result = importer.parse(JSON.stringify(v4)); + expect(result.sections.profiles.items[0].network).toBe("GitHub"); + expect(result.sections.profiles.items[0].username).toBe("janedoe"); + expect(result.sections.profiles.items[0].icon).toBe("github"); + }); + + it("transforms award items", () => { + const v4 = makeMinimalV4Resume(); + (v4.sections as any).awards.items = [ + { + id: "a1", + visible: true, + title: "Best Developer", + awarder: "Company", + date: "2023", + summary: "

    Won it

    ", + }, + ]; + + const result = importer.parse(JSON.stringify(v4)); + expect(result.sections.awards.items[0].title).toBe("Best Developer"); + expect(result.sections.awards.items[0].awarder).toBe("Company"); + }); + + it("transforms certification items", () => { + const v4 = makeMinimalV4Resume(); + (v4.sections as any).certifications.items = [ + { id: "c1", visible: true, name: "AWS Certified", issuer: "AWS", date: "2023" }, + ]; + + const result = importer.parse(JSON.stringify(v4)); + expect(result.sections.certifications.items[0].title).toBe("AWS Certified"); + expect(result.sections.certifications.items[0].issuer).toBe("AWS"); + }); + + it("transforms publication items", () => { + const v4 = makeMinimalV4Resume(); + (v4.sections as any).publications.items = [ + { id: "pub1", visible: true, name: "My Paper", publisher: "IEEE", date: "2024", summary: "

    Research

    " }, + ]; + + const result = importer.parse(JSON.stringify(v4)); + expect(result.sections.publications.items[0].title).toBe("My Paper"); + expect(result.sections.publications.items[0].publisher).toBe("IEEE"); + }); + + it("transforms volunteer items", () => { + const v4 = makeMinimalV4Resume(); + (v4.sections as any).volunteer.items = [ + { + id: "v1", + visible: true, + organization: "Red Cross", + position: "Helper", + location: "NYC", + date: "2023", + summary: "

    Helped

    ", + }, + ]; + + const result = importer.parse(JSON.stringify(v4)); + expect(result.sections.volunteer.items[0].organization).toBe("Red Cross"); + expect(result.sections.volunteer.items[0].location).toBe("NYC"); + }); + + it("transforms reference items", () => { + const v4 = makeMinimalV4Resume(); + (v4.sections as any).references.items = [ + { id: "r1", visible: true, name: "John Manager", description: "CTO", summary: "

    Great developer

    " }, + ]; + + const result = importer.parse(JSON.stringify(v4)); + expect(result.sections.references.items[0].name).toBe("John Manager"); + expect(result.sections.references.items[0].position).toBe("CTO"); + expect(result.sections.references.items[0].description).toBe("

    Great developer

    "); + }); + + it("transforms interest items", () => { + const v4 = makeMinimalV4Resume(); + (v4.sections as any).interests.items = [ + { id: "i1", visible: true, name: "Gaming", keywords: ["RPG", "Strategy"] }, + ]; + + const result = importer.parse(JSON.stringify(v4)); + expect(result.sections.interests.items[0].name).toBe("Gaming"); + expect(result.sections.interests.items[0].keywords).toEqual(["RPG", "Strategy"]); + }); + + it("transforms project items using summary over description", () => { + const v4 = makeMinimalV4Resume(); + (v4.sections as any).projects.items = [ + { + id: "proj1", + visible: true, + name: "My App", + description: "desc", + date: "2024", + summary: "

    summary

    ", + url: { label: "", href: "" }, + }, + ]; + + const result = importer.parse(JSON.stringify(v4)); + expect(result.sections.projects.items[0].description).toBe("

    summary

    "); + }); + + it("falls back to description when summary is missing for projects", () => { + const v4 = makeMinimalV4Resume(); + (v4.sections as any).projects.items = [ + { id: "proj1", visible: true, name: "My App", description: "desc text", date: "2024" }, + ]; + + const result = importer.parse(JSON.stringify(v4)); + expect(result.sections.projects.items[0].description).toBe("desc text"); + }); + }); + + // --------------------------------------------------------------------------- + // Filtering + // --------------------------------------------------------------------------- + + describe("filtering", () => { + it("filters out items with empty required fields", () => { + const v4 = makeMinimalV4Resume(); + (v4.sections as any).experience.items = [ + { id: "e1", visible: true, company: "Acme", position: "Dev" }, + { id: "e2", visible: true, company: "", position: "Dev" }, // empty company + { id: "e3", visible: true, position: "Dev" }, // missing company + ]; + + const result = importer.parse(JSON.stringify(v4)); + expect(result.sections.experience.items).toHaveLength(1); + expect(result.sections.experience.items[0].company).toBe("Acme"); + }); + + it("filters out profiles without network name", () => { + const v4 = makeMinimalV4Resume(); + (v4.sections as any).profiles.items = [ + { id: "p1", visible: true, network: "GitHub", username: "jane" }, + { id: "p2", visible: true, network: "", username: "jane" }, + ]; + + const result = importer.parse(JSON.stringify(v4)); + expect(result.sections.profiles.items).toHaveLength(1); + }); + + it("filters out skills without name", () => { + const v4 = makeMinimalV4Resume(); + (v4.sections as any).skills.items = [ + { id: "s1", visible: true, name: "TypeScript" }, + { id: "s2", visible: true, name: "" }, + ]; + + const result = importer.parse(JSON.stringify(v4)); + expect(result.sections.skills.items).toHaveLength(1); + }); + }); + + // --------------------------------------------------------------------------- + // Visibility mapping + // --------------------------------------------------------------------------- + + describe("visibility mapping", () => { + it("maps visible: false to hidden: true", () => { + const v4 = makeMinimalV4Resume(); + (v4.sections as any).experience.items = [{ id: "e1", visible: false, company: "Acme", position: "Dev" }]; + (v4.sections as any).experience.visible = false; + + const result = importer.parse(JSON.stringify(v4)); + expect(result.sections.experience.hidden).toBe(true); + expect(result.sections.experience.items[0].hidden).toBe(true); + }); + + it("maps visible: true to hidden: false", () => { + const v4 = makeMinimalV4Resume(); + (v4.sections as any).awards.visible = true; + + const result = importer.parse(JSON.stringify(v4)); + expect(result.sections.awards.hidden).toBe(false); + }); + }); + + // --------------------------------------------------------------------------- + // Metadata + // --------------------------------------------------------------------------- + + describe("metadata", () => { + it("maps theme colors to rgba format", () => { + const result = importer.parse(JSON.stringify(makeMinimalV4Resume())); + expect(result.metadata.design.colors.primary).toMatch(/^rgba\(/); + expect(result.metadata.design.colors.text).toMatch(/^rgba\(/); + expect(result.metadata.design.colors.background).toMatch(/^rgba\(/); + }); + + it("maps CSS settings", () => { + const v4 = makeMinimalV4Resume(); + v4.metadata.css = { value: "body { color: red; }", visible: true }; + + const result = importer.parse(JSON.stringify(v4)); + expect(result.metadata.css.enabled).toBe(true); + expect(result.metadata.css.value).toBe("body { color: red; }"); + }); + + it("maps page format", () => { + const v4 = makeMinimalV4Resume(); + (v4.metadata.page.format as "a4" | "letter") = "letter"; + + const result = importer.parse(JSON.stringify(v4)); + expect(result.metadata.page.format).toBe("letter"); + }); + + it("maps font family and font size (px to pt conversion)", () => { + const result = importer.parse(JSON.stringify(makeMinimalV4Resume())); + expect(result.metadata.typography.body.fontFamily).toBe("IBM Plex Serif"); + // 14.67px * 0.75 = 11.0025pt + expect(result.metadata.typography.body.fontSize).toBeCloseTo(11, 0); + }); + + it("maps hideIcons setting", () => { + const v4 = makeMinimalV4Resume(); + v4.metadata.typography.hideIcons = true; + + const result = importer.parse(JSON.stringify(v4)); + expect(result.metadata.page.hideIcons).toBe(true); + }); + + it("defaults to onyx template for invalid template names", () => { + const v4 = makeMinimalV4Resume(); + v4.metadata.template = "nonexistent-template"; + + const result = importer.parse(JSON.stringify(v4)); + expect(result.metadata.template).toBe("onyx"); + }); + + it("preserves valid template names", () => { + const v4 = makeMinimalV4Resume(); + v4.metadata.template = "pikachu"; + + const result = importer.parse(JSON.stringify(v4)); + expect(result.metadata.template).toBe("pikachu"); + }); + + it("maps notes", () => { + const v4 = makeMinimalV4Resume(); + v4.metadata.notes = "Some notes here"; + + const result = importer.parse(JSON.stringify(v4)); + expect(result.metadata.notes).toBe("Some notes here"); + }); + }); + + // --------------------------------------------------------------------------- + // Layout + // --------------------------------------------------------------------------- + + describe("layout", () => { + it("converts V4 layout pages to new format", () => { + const v4 = makeMinimalV4Resume(); + v4.metadata.layout = [ + [["experience", "education"], ["skills"]], + [["projects"], []], + ]; + + const result = importer.parse(JSON.stringify(v4)); + expect(result.metadata.layout.pages).toHaveLength(2); + expect(result.metadata.layout.pages[0].main).toContain("experience"); + expect(result.metadata.layout.pages[0].sidebar).toContain("skills"); + expect(result.metadata.layout.pages[1].main).toContain("projects"); + expect(result.metadata.layout.pages[1].fullWidth).toBe(true); // empty sidebar + }); + + it("strips 'custom.' prefix from layout section IDs", () => { + const v4 = makeMinimalV4Resume(); + v4.metadata.layout = [[["experience", "custom.abc123"], ["skills"]]]; + (v4.sections as any).custom = { + abc123: { + name: "Custom Section", + columns: 1, + separateLinks: false, + visible: true, + id: "abc123", + items: [], + }, + }; + + const result = importer.parse(JSON.stringify(v4)); + expect(result.metadata.layout.pages[0].main).toContain("abc123"); + expect(result.metadata.layout.pages[0].main).not.toContain("custom.abc123"); + }); + + it("removes summary from layout columns (handled separately)", () => { + const v4 = makeMinimalV4Resume(); + v4.metadata.layout = [[["summary", "experience"], ["skills"]]]; + + const result = importer.parse(JSON.stringify(v4)); + // Summary should be re-added at the front when visible + const mainSections = result.metadata.layout.pages[0].main; + expect(mainSections[0]).toBe("summary"); + expect(mainSections.filter((s) => s === "summary")).toHaveLength(1); + }); + + it("adds summary to front of first page when visible with content", () => { + const v4 = makeMinimalV4Resume(); + v4.metadata.layout = [[["experience"], ["skills"]]]; + (v4.sections as any).summary.visible = true; + (v4.sections as any).summary.content = "

    Summary text

    "; + + const result = importer.parse(JSON.stringify(v4)); + expect(result.metadata.layout.pages[0].main[0]).toBe("summary"); + }); + + it("does NOT add summary to layout when it is not visible", () => { + const v4 = makeMinimalV4Resume(); + v4.metadata.layout = [[["experience"], ["skills"]]]; + (v4.sections as any).summary.visible = false; + (v4.sections as any).summary.content = "

    Summary text

    "; + + const result = importer.parse(JSON.stringify(v4)); + expect(result.metadata.layout.pages[0].main).not.toContain("summary"); + }); + }); + + // --------------------------------------------------------------------------- + // Custom sections + // --------------------------------------------------------------------------- + + describe("custom sections", () => { + it("transforms custom sections to experience-typed custom sections", () => { + const v4 = makeMinimalV4Resume(); + (v4.sections as any).custom = { + "custom-1": { + name: "Freelance Work", + columns: 2, + separateLinks: false, + visible: true, + id: "custom-1", + items: [ + { + id: "ci1", + visible: true, + name: "Project A", + description: "Lead Dev", + date: "2023", + location: "Remote", + summary: "

    Details

    ", + }, + ], + }, + }; + + const result = importer.parse(JSON.stringify(v4)); + const section = result.customSections[0]; + const item = section.items[0] as SectionItem<"experience">; + + expect(result.customSections).toHaveLength(1); + expect(section.title).toBe("Freelance Work"); + expect(section.type).toBe("experience"); + expect(section.columns).toBe(2); + expect(section.items).toHaveLength(1); + expect(item.company).toBe("Project A"); + expect(item.position).toBe("Lead Dev"); + }); + + it("handles custom section items without name by using index", () => { + const v4 = makeMinimalV4Resume(); + (v4.sections as any).custom = { + "custom-1": { + name: "Other", + columns: 1, + separateLinks: false, + visible: true, + id: "custom-1", + items: [{ id: "ci1", visible: true, description: "Something" }], + }, + }; + + const result = importer.parse(JSON.stringify(v4)); + const section = result.customSections[0]; + const item = section.items[0] as SectionItem<"experience">; + + expect(item.company).toBe("#1"); + }); + + it("filters out invisible custom section items", () => { + const v4 = makeMinimalV4Resume(); + (v4.sections as any).custom = { + "custom-1": { + name: "Section", + columns: 1, + separateLinks: false, + visible: true, + id: "custom-1", + items: [ + { id: "ci1", visible: true, name: "Visible" }, + { id: "ci2", visible: false, name: "Hidden" }, + ], + }, + }; + + const result = importer.parse(JSON.stringify(v4)); + expect(result.customSections[0].items).toHaveLength(1); + }); + + it("handles empty custom sections object", () => { + const v4 = makeMinimalV4Resume(); + (v4.sections as any).custom = {}; + + const result = importer.parse(JSON.stringify(v4)); + expect(result.customSections).toHaveLength(0); + }); + + it("handles missing custom sections", () => { + const v4 = makeMinimalV4Resume(); + delete (v4.sections as any).custom; + + const result = importer.parse(JSON.stringify(v4)); + expect(result.customSections).toHaveLength(0); + }); + }); + + // --------------------------------------------------------------------------- + // Edge cases + // --------------------------------------------------------------------------- + + describe("edge cases", () => { + it("sanitizes invalid email addresses", () => { + const v4 = makeMinimalV4Resume(); + v4.basics.email = "not-an-email"; + + const result = importer.parse(JSON.stringify(v4)); + expect(result.basics.email).toBe(""); + }); + + it("preserves valid email addresses", () => { + const v4 = makeMinimalV4Resume(); + v4.basics.email = "valid@example.com"; + + const result = importer.parse(JSON.stringify(v4)); + expect(result.basics.email).toBe("valid@example.com"); + }); + + it("handles missing optional fields with defaults", () => { + const v4 = makeMinimalV4Resume(); + v4.basics.phone = ""; + v4.basics.location = ""; + v4.basics.url = { label: "", href: "" }; + + const result = importer.parse(JSON.stringify(v4)); + expect(result.basics.phone).toBe(""); + expect(result.basics.location).toBe(""); + expect(result.basics.website.url).toBe(""); + }); + + it("handles missing picture data", () => { + const v4 = makeMinimalV4Resume(); + (v4.basics as any).picture = undefined; + + const result = importer.parse(JSON.stringify(v4)); + expect(result.picture.url).toBe(""); + expect(result.picture.size).toBe(80); + }); + + it("clamps picture size to valid range", () => { + const v4 = makeMinimalV4Resume(); + v4.basics.picture.size = 1000; + + const result = importer.parse(JSON.stringify(v4)); + expect(result.picture.size).toBe(512); + }); + + it("clamps picture size minimum", () => { + const v4 = makeMinimalV4Resume(); + v4.basics.picture.size = 5; + + const result = importer.parse(JSON.stringify(v4)); + expect(result.picture.size).toBe(32); + }); + + it("clamps aspect ratio to valid range", () => { + const v4 = makeMinimalV4Resume(); + v4.basics.picture.aspectRatio = 10; + + const result = importer.parse(JSON.stringify(v4)); + expect(result.picture.aspectRatio).toBe(2.5); + }); + + it("converts font variants correctly", () => { + const v4 = makeMinimalV4Resume(); + v4.metadata.typography.font.variants = ["regular", "bold", "700"]; + + const result = importer.parse(JSON.stringify(v4)); + expect(result.metadata.typography.body.fontWeights).toContain("400"); + expect(result.metadata.typography.body.fontWeights).toContain("700"); + }); + + it("heading font weights are >= 600", () => { + const v4 = makeMinimalV4Resume(); + v4.metadata.typography.font.variants = ["regular", "300"]; + + const result = importer.parse(JSON.stringify(v4)); + // All heading weights should be >= 600; defaults to ["600"] if none qualify + for (const weight of result.metadata.typography.heading.fontWeights) { + expect(Number.parseInt(weight, 10)).toBeGreaterThanOrEqual(600); + } + }); + + it("handles custom fields in basics", () => { + const v4 = makeMinimalV4Resume(); + (v4.basics.customFields as any) = [{ id: "cf1", icon: "star", text: "Custom field value" }]; + + const result = importer.parse(JSON.stringify(v4)); + expect(result.basics.customFields).toHaveLength(1); + expect(result.basics.customFields[0].text).toBe("Custom field value"); + expect(result.basics.customFields[0].icon).toBe("star"); + }); + + it("throws on invalid JSON", () => { + expect(() => importer.parse("not json")).toThrow(); + }); + + it("produces a result that validates against resumeDataSchema", () => { + const v4 = makeMinimalV4Resume(); + // Add items to every section + (v4.sections as any).experience.items = [{ id: "e1", visible: true, company: "Co", position: "Dev" }]; + (v4.sections as any).education.items = [{ id: "ed1", visible: true, institution: "Uni", studyType: "BSc" }]; + (v4.sections as any).skills.items = [{ id: "s1", visible: true, name: "JS", level: 3 }]; + (v4.sections as any).languages.items = [{ id: "l1", visible: true, name: "English", level: 5 }]; + + const result = importer.parse(JSON.stringify(v4)); + const validation = resumeDataSchema.safeParse(result); + expect(validation.success).toBe(true); + }); + }); + + // --------------------------------------------------------------------------- + // Font weight conversion + // --------------------------------------------------------------------------- + + describe("font weight conversion", () => { + it("maps 'bold' to 700", () => { + const v4 = makeMinimalV4Resume(); + v4.metadata.typography.font.variants = ["bold"]; + + const result = importer.parse(JSON.stringify(v4)); + expect(result.metadata.typography.body.fontWeights).toContain("700"); + }); + + it("maps 'italic' to 400", () => { + const v4 = makeMinimalV4Resume(); + v4.metadata.typography.font.variants = ["italic"]; + + const result = importer.parse(JSON.stringify(v4)); + expect(result.metadata.typography.body.fontWeights).toContain("400"); + }); + + it("defaults to 400 for empty variants", () => { + const v4 = makeMinimalV4Resume(); + v4.metadata.typography.font.variants = []; + + const result = importer.parse(JSON.stringify(v4)); + expect(result.metadata.typography.body.fontWeights).toEqual(["400"]); + }); + }); +}); diff --git a/src/integrations/jobs/store.test.ts b/src/integrations/jobs/store.test.ts new file mode 100644 index 000000000..ce2a87709 --- /dev/null +++ b/src/integrations/jobs/store.test.ts @@ -0,0 +1,93 @@ +import { afterEach, describe, expect, it } from "vite-plus/test"; + +import { useJobsStore } from "./store"; + +afterEach(() => { + useJobsStore.getState().reset(); +}); + +// --------------------------------------------------------------------------- +// Initial state +// --------------------------------------------------------------------------- + +describe("Jobs Store — initial state", () => { + it("starts with default values", () => { + const state = useJobsStore.getState(); + expect(state.rapidApiKey).toBe(""); + expect(state.testStatus).toBe("unverified"); + expect(state.rapidApiQuota).toBeNull(); + }); +}); + +// --------------------------------------------------------------------------- +// set() +// --------------------------------------------------------------------------- + +describe("Jobs Store — set()", () => { + it("updates rapidApiKey", () => { + useJobsStore.getState().set((draft) => { + draft.rapidApiKey = "new-api-key"; + }); + expect(useJobsStore.getState().rapidApiKey).toBe("new-api-key"); + }); + + it("resets testStatus to unverified when rapidApiKey changes", () => { + useJobsStore.getState().set((draft) => { + draft.testStatus = "success"; + }); + useJobsStore.getState().set((draft) => { + draft.rapidApiKey = "different-key"; + }); + expect(useJobsStore.getState().testStatus).toBe("unverified"); + }); + + it("does NOT reset testStatus when rapidApiKey stays the same", () => { + useJobsStore.getState().set((draft) => { + draft.rapidApiKey = "same-key"; + }); + useJobsStore.getState().set((draft) => { + draft.testStatus = "success"; + }); + // Change something else, keep the same key + useJobsStore.getState().set((draft) => { + draft.rapidApiQuota = { used: 10, limit: 100, remaining: 90 }; + }); + expect(useJobsStore.getState().testStatus).toBe("success"); + }); + + it("updates rapidApiQuota", () => { + const quota = { used: 50, limit: 100, remaining: 50 }; + useJobsStore.getState().set((draft) => { + draft.rapidApiQuota = quota; + }); + expect(useJobsStore.getState().rapidApiQuota).toEqual(quota); + }); + + it("updates testStatus directly", () => { + useJobsStore.getState().set((draft) => { + draft.testStatus = "failure"; + }); + expect(useJobsStore.getState().testStatus).toBe("failure"); + }); +}); + +// --------------------------------------------------------------------------- +// reset() +// --------------------------------------------------------------------------- + +describe("Jobs Store — reset()", () => { + it("restores all state to initial values", () => { + useJobsStore.getState().set((draft) => { + draft.rapidApiKey = "some-key"; + draft.testStatus = "success"; + draft.rapidApiQuota = { used: 10, limit: 100, remaining: 90 }; + }); + + useJobsStore.getState().reset(); + + const state = useJobsStore.getState(); + expect(state.rapidApiKey).toBe(""); + expect(state.testStatus).toBe("unverified"); + expect(state.rapidApiQuota).toBeNull(); + }); +}); diff --git a/src/integrations/orpc/context.ts b/src/integrations/orpc/context.ts index 9906b05b8..6640072f1 100644 --- a/src/integrations/orpc/context.ts +++ b/src/integrations/orpc/context.ts @@ -26,7 +26,8 @@ async function getUserFromBearerToken(headers: Headers): Promise { const [userResult] = await db.select().from(user).where(eq(user.id, payload.sub)).limit(1); return userResult ?? null; - } catch { + } catch (error) { + console.warn("Bearer token verification failed:", error); return null; } } @@ -37,7 +38,8 @@ async function getUserFromHeaders(headers: Headers): Promise { if (!result || !result.user) return null; return result.user; - } catch { + } catch (error) { + console.warn("Session verification failed:", error); return null; } } @@ -51,7 +53,8 @@ async function getUserFromApiKey(apiKey: string): Promise { if (!userResult) return null; return userResult; - } catch { + } catch (error) { + console.warn("API key verification failed:", error); return null; } } diff --git a/src/integrations/orpc/helpers/resume-access.test.ts b/src/integrations/orpc/helpers/resume-access.test.ts new file mode 100644 index 000000000..d614a2fb4 --- /dev/null +++ b/src/integrations/orpc/helpers/resume-access.test.ts @@ -0,0 +1,106 @@ +import { createHash } from "node:crypto"; +import { describe, expect, it } from "vite-plus/test"; + +// --------------------------------------------------------------------------- +// We test the pure crypto helpers directly (signResumeAccessToken, safeEquals) +// since they don't depend on cookies or env. +// The actual module uses private functions, so we reimplement and test the algorithm. +// --------------------------------------------------------------------------- + +function signResumeAccessToken(resumeId: string, passwordHash: string): string { + return createHash("sha256").update(`${resumeId}:${passwordHash}`).digest("hex"); +} + +function safeEquals(value: string, expected: string): boolean { + const { timingSafeEqual } = require("node:crypto"); + const valueBuffer = Buffer.from(value); + const expectedBuffer = Buffer.from(expected); + if (valueBuffer.length !== expectedBuffer.length) return false; + return timingSafeEqual(valueBuffer, expectedBuffer); +} + +function getResumeAccessCookieName(resumeId: string): string { + return `resume_access_${resumeId}`; +} + +// --------------------------------------------------------------------------- +// signResumeAccessToken +// --------------------------------------------------------------------------- + +describe("signResumeAccessToken", () => { + it("produces a hex SHA256 hash", () => { + const token = signResumeAccessToken("resume-123", "hashed-password"); + expect(token).toMatch(/^[0-9a-f]{64}$/); + }); + + it("produces consistent results for same inputs", () => { + const t1 = signResumeAccessToken("resume-1", "pw-hash"); + const t2 = signResumeAccessToken("resume-1", "pw-hash"); + expect(t1).toBe(t2); + }); + + it("produces different tokens for different resume IDs", () => { + const t1 = signResumeAccessToken("resume-1", "pw-hash"); + const t2 = signResumeAccessToken("resume-2", "pw-hash"); + expect(t1).not.toBe(t2); + }); + + it("produces different tokens for different password hashes", () => { + const t1 = signResumeAccessToken("resume-1", "hash-a"); + const t2 = signResumeAccessToken("resume-1", "hash-b"); + expect(t1).not.toBe(t2); + }); + + it("handles empty inputs", () => { + const token = signResumeAccessToken("", ""); + expect(token).toMatch(/^[0-9a-f]{64}$/); + }); +}); + +// --------------------------------------------------------------------------- +// safeEquals +// --------------------------------------------------------------------------- + +describe("safeEquals", () => { + it("returns true for equal strings", () => { + expect(safeEquals("abc123", "abc123")).toBe(true); + }); + + it("returns false for different strings of same length", () => { + expect(safeEquals("abc123", "abc456")).toBe(false); + }); + + it("returns false for strings of different lengths", () => { + expect(safeEquals("short", "much-longer-string")).toBe(false); + }); + + it("returns true for empty strings", () => { + expect(safeEquals("", "")).toBe(true); + }); + + it("returns false for empty vs non-empty", () => { + expect(safeEquals("", "notempty")).toBe(false); + }); + + it("handles long hex strings (like SHA256 hashes)", () => { + const hash = signResumeAccessToken("id", "pw"); + expect(safeEquals(hash, hash)).toBe(true); + expect(safeEquals(hash, hash.replace(/.$/, "0"))).toBe(false); + }); +}); + +// --------------------------------------------------------------------------- +// getResumeAccessCookieName +// --------------------------------------------------------------------------- + +describe("getResumeAccessCookieName", () => { + it("prefixes resume ID with resume_access_", () => { + expect(getResumeAccessCookieName("abc-123")).toBe("resume_access_abc-123"); + }); + + it("handles various ID formats", () => { + expect(getResumeAccessCookieName("01234567-89ab-cdef-0123-456789abcdef")).toBe( + "resume_access_01234567-89ab-cdef-0123-456789abcdef", + ); + }); +}); diff --git a/src/integrations/orpc/router/auth.ts b/src/integrations/orpc/router/auth.ts index 164a1808d..5853a1e71 100644 --- a/src/integrations/orpc/router/auth.ts +++ b/src/integrations/orpc/router/auth.ts @@ -11,7 +11,7 @@ export const authRouter = { operationId: "listAuthProviders", summary: "List authentication providers", description: - "Returns a list of all authentication providers enabled on this Reactive Resume instance, along with their display names. Possible providers include password-based credentials, Google, GitHub, and custom OAuth. No authentication required.", + "Returns a list of all authentication providers enabled on this Reactive Resume instance, along with their display names. Possible providers include password-based credentials, Google, GitHub, LinkedIn, and custom OAuth. No authentication required.", successDescription: "A map of enabled authentication provider identifiers to their display names.", }) .handler((): ProviderList => { diff --git a/src/integrations/orpc/services/ai.ts b/src/integrations/orpc/services/ai.ts index 43e4c1dc6..8370298f5 100644 --- a/src/integrations/orpc/services/ai.ts +++ b/src/integrations/orpc/services/ai.ts @@ -33,108 +33,10 @@ import { } from "@/integrations/ai/tools/patch-resume"; import { defaultResumeData, resumeDataSchema } from "@/schema/resume/data"; import { type TailorOutput, tailorOutputSchema } from "@/schema/tailor"; +import { buildAiExtractionTemplate } from "@/utils/ai-template"; import { isObject } from "@/utils/sanitize"; -const aiExtractionTemplate = { - ...defaultResumeData, - basics: { - ...defaultResumeData.basics, - customFields: [{ id: "", icon: "", text: "", link: "" }], - }, - sections: { - ...defaultResumeData.sections, - profiles: { - ...defaultResumeData.sections.profiles, - items: [{ id: "", hidden: false, icon: "", network: "", username: "", website: { url: "", label: "" } }], - }, - experience: { - ...defaultResumeData.sections.experience, - items: [ - { - id: "", - hidden: false, - company: "", - position: "", - location: "", - period: "", - website: { url: "", label: "" }, - description: "", - }, - ], - }, - education: { - ...defaultResumeData.sections.education, - items: [ - { - id: "", - hidden: false, - school: "", - degree: "", - area: "", - grade: "", - location: "", - period: "", - website: { url: "", label: "" }, - description: "", - }, - ], - }, - projects: { - ...defaultResumeData.sections.projects, - items: [{ id: "", hidden: false, name: "", period: "", website: { url: "", label: "" }, description: "" }], - }, - skills: { - ...defaultResumeData.sections.skills, - items: [{ id: "", hidden: false, icon: "", name: "", proficiency: "", level: 0, keywords: [] }], - }, - languages: { - ...defaultResumeData.sections.languages, - items: [{ id: "", hidden: false, language: "", fluency: "", level: 0 }], - }, - interests: { - ...defaultResumeData.sections.interests, - items: [{ id: "", hidden: false, icon: "", name: "", keywords: [] }], - }, - awards: { - ...defaultResumeData.sections.awards, - items: [ - { id: "", hidden: false, title: "", awarder: "", date: "", website: { url: "", label: "" }, description: "" }, - ], - }, - certifications: { - ...defaultResumeData.sections.certifications, - items: [ - { id: "", hidden: false, title: "", issuer: "", date: "", website: { url: "", label: "" }, description: "" }, - ], - }, - publications: { - ...defaultResumeData.sections.publications, - items: [ - { id: "", hidden: false, title: "", publisher: "", date: "", website: { url: "", label: "" }, description: "" }, - ], - }, - volunteer: { - ...defaultResumeData.sections.volunteer, - items: [ - { - id: "", - hidden: false, - organization: "", - location: "", - period: "", - website: { url: "", label: "" }, - description: "", - }, - ], - }, - references: { - ...defaultResumeData.sections.references, - items: [ - { id: "", hidden: false, name: "", position: "", website: { url: "", label: "" }, phone: "", description: "" }, - ], - }, - }, -}; +const aiExtractionTemplate = buildAiExtractionTemplate(); /** * Merges two objects recursively, filling in missing properties in the target object diff --git a/src/integrations/orpc/services/auth.ts b/src/integrations/orpc/services/auth.ts index a1b11cd9d..205bd5ad6 100644 --- a/src/integrations/orpc/services/auth.ts +++ b/src/integrations/orpc/services/auth.ts @@ -17,6 +17,7 @@ const providers = { if (env.GOOGLE_CLIENT_ID && env.GOOGLE_CLIENT_SECRET) providers.google = "Google"; if (env.GITHUB_CLIENT_ID && env.GITHUB_CLIENT_SECRET) providers.github = "GitHub"; + if (env.LINKEDIN_CLIENT_ID && env.LINKEDIN_CLIENT_SECRET) providers.linkedin = "LinkedIn"; if (env.OAUTH_CLIENT_ID && env.OAUTH_CLIENT_SECRET) providers.custom = env.OAUTH_PROVIDER_NAME ?? "Custom OAuth"; return providers; diff --git a/src/integrations/orpc/services/printer.ts b/src/integrations/orpc/services/printer.ts index c68d941c6..fb861e0f2 100644 --- a/src/integrations/orpc/services/printer.ts +++ b/src/integrations/orpc/services/printer.ts @@ -3,7 +3,7 @@ import type { InferSelectModel } from "drizzle-orm"; import { ORPCError } from "@orpc/server"; import dns from "node:dns/promises"; import { isIP } from "node:net"; -import puppeteer, { type Browser, type ConnectOptions, type Page } from "puppeteer-core"; +import puppeteer, { type Browser, type BrowserContext, type ConnectOptions, type Page } from "puppeteer-core"; import type { schema } from "@/integrations/drizzle"; @@ -16,6 +16,10 @@ import { getStorageService, uploadFile } from "./storage"; const SCREENSHOT_TTL = 1000 * 60 * 60 * 6; // 6 hours +// Deduplicate concurrent requests for the same resume +const activePrintJobs = new Map>(); +const activeScreenshotJobs = new Map>(); + // Singleton browser instance for connection reuse let browserInstance: Browser | null = null; @@ -59,15 +63,312 @@ async function closeBrowser(): Promise { } // Close browser on process termination -process.on("SIGINT", async () => { +process.on("exit", async () => { await closeBrowser(); process.exit(0); }); -process.on("SIGTERM", async () => { - await closeBrowser(); - process.exit(0); -}); +/** + * Generates a PDF from a resume and uploads it to storage. + * + * The process: + * 1. Clean up any existing PDF for this resume + * 2. Navigate to the printer route which renders the resume + * 3. Calculate PDF margins (some templates require margins to be applied via PDF) + * 4. Adjust CSS variables so content fits within printable area (accounting for margins) + * 5. Add page break CSS to ensure each visual resume page becomes a PDF page + * 6. Generate the PDF with proper dimensions and margins + * 7. Upload to storage and return the URL + */ +async function doPrintResumeAsPDF( + input: Pick, "id" | "data" | "userId">, +): Promise { + const { id, data, userId } = input; + + // Step 1: Delete any existing PDF for this resume to ensure fresh generation + const storageService = getStorageService(); + const pdfPrefix = `uploads/${userId}/pdfs/${id}`; + await storageService.delete(pdfPrefix); + + // Step 2: Prepare the URL and authentication for the printer route + // The printer route renders the resume in a format optimized for PDF generation + const baseUrl = env.PRINTER_APP_URL ?? env.APP_URL; + const domain = new URL(baseUrl).hostname; + + const format = data.metadata.page.format; + const locale = data.metadata.page.locale; + const template = data.metadata.template; + + // Generate a secure token to authenticate the printer request + const token = generatePrinterToken(id); + const url = `${baseUrl}/printer/${id}?token=${token}`; + + // Step 3: Calculate print paddings for templates that disable CSS padding in print mode. + // We render these margins inside the page (not via Puppeteer's PDF margins), so the margin + // area matches the resume background color instead of staying white. + let pagePaddingX = 0; + let pagePaddingY = 0; + + if (printMarginTemplates.includes(template)) { + pagePaddingX = data.metadata.page.marginX; + pagePaddingY = data.metadata.page.marginY; + } + + let context: BrowserContext | null = null; + let page: Page | null = null; + + try { + // Step 4: Connect to the browser and navigate to the printer route + // Use an isolated browser context so concurrent requests with different locales don't interfere + const browser = await getBrowser(); + context = await browser.createBrowserContext(); + + await context.setCookie({ name: "locale", value: locale, domain }); + + page = await context.newPage(); + + // Wait for the page to fully load (network idle + custom loaded attribute) + await page.emulateMediaType("print"); + await page.setViewport(pageDimensionsAsPixels[format]); + await page.goto(url, { waitUntil: "networkidle0" }); + await page.waitForFunction(() => document.body.getAttribute("data-wf-loaded") === "true", { timeout: 5_000 }); + + // Step 5a: Prepare the DOM for PDF rendering (background colors, reset margins, print padding) + await page.evaluate( + (pagePaddingX: number, pagePaddingY: number, backgroundColor: string) => { + const root = document.documentElement; + const body = document.body; + const pageElements = document.querySelectorAll("[data-page-index]"); + const pageContentElements = document.querySelectorAll(".page-content"); + + // Ensure PDF margins inherit the resume background color instead of defaulting to white. + root.style.backgroundColor = backgroundColor; + body.style.backgroundColor = backgroundColor; + root.style.margin = "0"; + body.style.margin = "0"; + root.style.padding = "0"; + body.style.padding = "0"; + + for (const el of pageElements) { + const pageWrapper = el as HTMLElement; + const pageSurface = pageWrapper.querySelector(".page") as HTMLElement | null; + + pageWrapper.style.backgroundColor = backgroundColor; + pageWrapper.style.breakInside = "auto"; + + if (pageSurface) pageSurface.style.backgroundColor = backgroundColor; + } + + // Apply print-only margins as padding inside each page's content surface. + if (pagePaddingX > 0 || pagePaddingY > 0) { + for (const el of pageContentElements) { + const pageContent = el as HTMLElement; + + pageContent.style.boxSizing = "border-box"; + // Ensure padding is repeated on every printed fragment when content + // flows across physical PDF pages (not just the first fragment). + pageContent.style.boxDecorationBreak = "clone"; + pageContent.style.setProperty("-webkit-box-decoration-break", "clone"); + if (pagePaddingX > 0) { + pageContent.style.paddingLeft = `${pagePaddingX}pt`; + pageContent.style.paddingRight = `${pagePaddingX}pt`; + } + if (pagePaddingY > 0) { + pageContent.style.paddingTop = `${pagePaddingY}pt`; + pageContent.style.paddingBottom = `${pagePaddingY}pt`; + } + } + } + }, + pagePaddingX, + pagePaddingY, + data.metadata.design.colors.background, + ); + + // Step 5b: Format-specific layout adjustments + const isFreeForm = format === "free-form"; + let contentHeight: number | null = null; + + if (isFreeForm) { + // Free-form: measure actual content height after adding inter-page margins + contentHeight = await page.evaluate( + (pagePaddingY: number, minPageHeight: number) => { + const pageElements = document.querySelectorAll("[data-page-index]"); + const numberOfPages = pageElements.length; + + // Add margin between pages (except the last one) + for (let i = 0; i < numberOfPages - 1; i++) { + const pageEl = pageElements[i] as HTMLElement; + if (pagePaddingY > 0) pageEl.style.marginBottom = `${pagePaddingY}pt`; + } + + // Measure the total height (margins are now part of the DOM) + let totalHeight = 0; + + for (const el of pageElements) { + const pageEl = el as HTMLElement; + const style = getComputedStyle(pageEl); + const marginBottom = Number.parseFloat(style.marginBottom) || 0; + totalHeight += pageEl.offsetHeight + marginBottom; + } + + return Math.max(totalHeight, minPageHeight); + }, + pagePaddingY, + pageDimensionsAsPixels[format].height, + ); + } else { + // A4/Letter: set fixed page height and add page breaks between pages + await page.evaluate((pageHeight: number) => { + const root = document.documentElement; + const pageElements = document.querySelectorAll("[data-page-index]"); + const container = document.querySelector(".resume-preview-container") as HTMLElement | null; + + const newHeight = `${pageHeight}px`; + if (container) container.style.setProperty("--page-height", newHeight); + root.style.setProperty("--page-height", newHeight); + + for (const el of pageElements) { + const element = el as HTMLElement; + const index = Number.parseInt(element.getAttribute("data-page-index") ?? "0", 10); + + // Force a page break before each page except the first + if (index > 0) { + element.style.breakBefore = "page"; + element.style.pageBreakBefore = "always"; + } + + // Allow content within a page to break naturally if it overflows + element.style.breakInside = "auto"; + } + }, pageDimensionsAsPixels[format].height); + } + + // Step 6: Generate the PDF with the specified dimensions and margins + // For free-form: use measured content height (with minimum constraint) + // For A4/Letter: use fixed dimensions from pageDimensionsAsPixels + const pdfHeight = isFreeForm && contentHeight ? contentHeight : pageDimensionsAsPixels[format].height; + + const pdfBuffer = await page.pdf({ + width: `${pageDimensionsAsPixels[format].width}px`, + height: `${pdfHeight}px`, + tagged: true, // Adds accessibility tags to the PDF + waitForFonts: true, // Ensures all fonts are loaded before rendering + printBackground: true, // Includes background colors and images + margin: { + bottom: 0, + top: 0, + right: 0, + left: 0, + }, + }); + + // Step 7: Upload the generated PDF to storage + const result = await uploadFile({ + userId, + resumeId: id, + data: new Uint8Array(pdfBuffer), + contentType: "application/pdf", + type: "pdf", + }); + + return result.url; + } catch (error) { + throw new ORPCError("INTERNAL_SERVER_ERROR", { message: "Failed to generate PDF", cause: error }); + } finally { + if (page) await page.close().catch(() => null); + if (context) await context.close().catch(() => null); + } +} + +/** + * Captures a screenshot of the first page of a resume as WebP. + * + * Uses a timestamp-based cache (6-hour TTL) to avoid regenerating screenshots + * for resumes that haven't changed. Old screenshots are cleaned up on regeneration. + */ +async function doGetResumeScreenshot( + input: Pick, "userId" | "id" | "data" | "updatedAt">, +): Promise { + const { id, userId, data, updatedAt } = input; + + const storageService = getStorageService(); + const screenshotPrefix = `uploads/${userId}/screenshots/${id}`; + + const existingScreenshots = await storageService.list(screenshotPrefix); + const now = Date.now(); + const resumeUpdatedAt = updatedAt.getTime(); + + if (existingScreenshots.length > 0) { + const sortedFiles = existingScreenshots + .map((path) => { + const filename = path.split("/").pop(); + const match = filename?.match(/^(\d+)\.webp$/); + return match ? { path, timestamp: Number(match[1]) } : null; + }) + .filter((item): item is { path: string; timestamp: number } => item !== null) + .sort((a, b) => b.timestamp - a.timestamp); + + if (sortedFiles.length > 0) { + const latest = sortedFiles[0]; + const age = now - latest.timestamp; + + // Return existing screenshot if it's still fresh (within TTL) + if (age < SCREENSHOT_TTL) return new URL(latest.path, env.APP_URL).toString(); + + // Screenshot is stale (past TTL), but only regenerate if the resume + // was updated after the screenshot was taken. If the resume hasn't + // changed, keep using the existing screenshot to avoid unnecessary work. + if (resumeUpdatedAt <= latest.timestamp) { + return new URL(latest.path, env.APP_URL).toString(); + } + + // Resume was updated after the screenshot - delete old ones and regenerate + await Promise.all(sortedFiles.map((file) => storageService.delete(file.path))); + } + } + + const baseUrl = env.PRINTER_APP_URL ?? env.APP_URL; + const domain = new URL(baseUrl).hostname; + + const locale = data.metadata.page.locale; + + const token = generatePrinterToken(id); + const url = `${baseUrl}/printer/${id}?token=${token}`; + + let context: BrowserContext | null = null; + let page: Page | null = null; + + try { + const browser = await getBrowser(); + context = await browser.createBrowserContext(); + + await context.setCookie({ name: "locale", value: locale, domain }); + + page = await context.newPage(); + + await page.setViewport(pageDimensionsAsPixels.a4); + await page.goto(url, { waitUntil: "networkidle0" }); + await page.waitForFunction(() => document.body.getAttribute("data-wf-loaded") === "true", { timeout: 5_000 }); + + const screenshotBuffer = await page.screenshot({ type: "webp", quality: 80 }); + + const result = await uploadFile({ + userId, + resumeId: id, + data: new Uint8Array(screenshotBuffer), + contentType: "image/webp", + type: "screenshot", + }); + + return result.url; + } catch (error) { + throw new ORPCError("INTERNAL_SERVER_ERROR", { message: "Failed to capture screenshot", cause: error }); + } finally { + if (page) await page.close().catch(() => null); + if (context) await context.close().catch(() => null); + } +} export const printerService = { healthcheck: async (): Promise => { @@ -83,300 +384,39 @@ export const printerService = { return data; }, - /** - * Generates a PDF from a resume and uploads it to storage. - * - * The process: - * 1. Clean up any existing PDF for this resume - * 2. Navigate to the printer route which renders the resume - * 3. Calculate PDF margins (some templates require margins to be applied via PDF) - * 4. Adjust CSS variables so content fits within printable area (accounting for margins) - * 5. Add page break CSS to ensure each visual resume page becomes a PDF page - * 6. Generate the PDF with proper dimensions and margins - * 7. Upload to storage and return the URL - */ + /** Generates a PDF, deduplicating concurrent requests for the same resume. */ printResumeAsPDF: async ( input: Pick, "id" | "data" | "userId">, ): Promise => { - const { id, data, userId } = input; + const { id } = input; - // Step 1: Delete any existing PDF for this resume to ensure fresh generation - const storageService = getStorageService(); - const pdfPrefix = `uploads/${userId}/pdfs/${id}`; - await storageService.delete(pdfPrefix); + // Deduplicate concurrent requests for the same resume + const existing = activePrintJobs.get(id); + if (existing) return existing; - // Step 2: Prepare the URL and authentication for the printer route - // The printer route renders the resume in a format optimized for PDF generation - const baseUrl = env.PRINTER_APP_URL ?? env.APP_URL; - const domain = new URL(baseUrl).hostname; + const job = doPrintResumeAsPDF(input).finally(() => { + activePrintJobs.delete(id); + }); - const format = data.metadata.page.format; - const locale = data.metadata.page.locale; - const template = data.metadata.template; - - // Generate a secure token to authenticate the printer request - const token = generatePrinterToken(id); - const url = `${baseUrl}/printer/${id}?token=${token}`; - - // Step 3: Calculate print paddings for templates that disable CSS padding in print mode. - // We render these margins inside the page (not via Puppeteer's PDF margins), so the margin - // area matches the resume background color instead of staying white. - let pagePaddingX = 0; - let pagePaddingY = 0; - - if (printMarginTemplates.includes(template)) { - pagePaddingX = data.metadata.page.marginX; - pagePaddingY = data.metadata.page.marginY; - } - - let browser: Browser | null = null; - let page: Page | null = null; - - try { - // Step 4: Connect to the browser and navigate to the printer route - browser = await getBrowser(); - - // Set locale cookie so the resume renders in the correct language - await browser.setCookie({ name: "locale", value: locale, domain }); - - page = await browser.newPage(); - - // Wait for the page to fully load (network idle + custom loaded attribute) - await page.emulateMediaType("print"); - await page.setViewport(pageDimensionsAsPixels[format]); - await page.goto(url, { waitUntil: "networkidle0" }); - await page.waitForFunction(() => document.body.getAttribute("data-wf-loaded") === "true", { timeout: 5_000 }); - - // Step 5: Adjust the DOM for proper PDF pagination - // This runs in the browser context to modify CSS before PDF generation - // For free-form: measure actual content height, don't add page breaks - // For A4/Letter: adjust page height for margins, add page breaks - const isFreeForm = format === "free-form"; - - const contentHeight = await page.evaluate( - ( - pagePaddingX: number, - pagePaddingY: number, - isFreeForm: boolean, - minPageHeight: number, - backgroundColor: string, - ) => { - const root = document.documentElement; - const body = document.body; - const pageElements = document.querySelectorAll("[data-page-index]"); - const pageContentElements = document.querySelectorAll(".page-content"); - const container = document.querySelector(".resume-preview-container") as HTMLElement | null; - - // Ensure PDF margins inherit the resume background color instead of defaulting to white. - root.style.backgroundColor = backgroundColor; - body.style.backgroundColor = backgroundColor; - root.style.margin = "0"; - body.style.margin = "0"; - root.style.padding = "0"; - body.style.padding = "0"; - - for (const el of pageElements) { - const pageWrapper = el as HTMLElement; - const pageSurface = pageWrapper.querySelector(".page") as HTMLElement | null; - - pageWrapper.style.backgroundColor = backgroundColor; - pageWrapper.style.breakInside = "auto"; - - if (pageSurface) pageSurface.style.backgroundColor = backgroundColor; - } - - // Apply print-only margins as padding inside each page's content surface. - if (pagePaddingX > 0 || pagePaddingY > 0) { - for (const el of pageContentElements) { - const pageContent = el as HTMLElement; - - pageContent.style.boxSizing = "border-box"; - // Ensure padding is repeated on every printed fragment when content - // flows across physical PDF pages (not just the first fragment). - pageContent.style.boxDecorationBreak = "clone"; - pageContent.style.setProperty("-webkit-box-decoration-break", "clone"); - if (pagePaddingX > 0) { - pageContent.style.paddingLeft = `${pagePaddingX}pt`; - pageContent.style.paddingRight = `${pagePaddingX}pt`; - } - if (pagePaddingY > 0) { - pageContent.style.paddingTop = `${pagePaddingY}pt`; - pageContent.style.paddingBottom = `${pagePaddingY}pt`; - } - } - } - - if (isFreeForm) { - const numberOfPages = pageElements.length; - - // Add margin between pages (except the last one) - for (let i = 0; i < numberOfPages - 1; i++) { - const pageEl = pageElements[i] as HTMLElement; - if (pagePaddingY > 0) pageEl.style.marginBottom = `${pagePaddingY}pt`; - } - - // Now measure the total height (margins are now part of the DOM) - let totalHeight = 0; - - for (const el of pageElements) { - const pageEl = el as HTMLElement; - // offsetHeight includes padding and border, but not margin - const style = getComputedStyle(pageEl); - const marginBottom = Number.parseFloat(style.marginBottom) || 0; - totalHeight += pageEl.offsetHeight + marginBottom; - } - - return Math.max(totalHeight, minPageHeight); - } - - // For A4/Letter - const heightValue = minPageHeight; - - // Keep page height fixed and let in-page padding (if any) define content bounds. - const newHeight = `${heightValue}px`; - if (container) container.style.setProperty("--page-height", newHeight); - root.style.setProperty("--page-height", newHeight); - - // Add page break CSS to each resume page element (identified by data-page-index attribute) - // This ensures each visual resume page starts a new PDF page - for (const el of pageElements) { - const element = el as HTMLElement; - const index = Number.parseInt(element.getAttribute("data-page-index") ?? "0", 10); - - // Force a page break before each page except the first - if (index > 0) { - element.style.breakBefore = "page"; - element.style.pageBreakBefore = "always"; - } - - // Allow content within a page to break naturally if it overflows - // (e.g., if a single page has more content than fits on one PDF page) - element.style.breakInside = "auto"; - } - - return null; // Fixed height from pageDimensionsAsPixels for A4/Letter - }, - pagePaddingX, - pagePaddingY, - isFreeForm, - pageDimensionsAsPixels[format].height, - data.metadata.design.colors.background, - ); - - // Step 6: Generate the PDF with the specified dimensions and margins - // For free-form: use measured content height (with minimum constraint) - // For A4/Letter: use fixed dimensions from pageDimensionsAsPixels - const pdfHeight = isFreeForm && contentHeight ? contentHeight : pageDimensionsAsPixels[format].height; - - const pdfBuffer = await page.pdf({ - width: `${pageDimensionsAsPixels[format].width}px`, - height: `${pdfHeight}px`, - tagged: true, // Adds accessibility tags to the PDF - waitForFonts: true, // Ensures all fonts are loaded before rendering - printBackground: true, // Includes background colors and images - margin: { - bottom: 0, - top: 0, - right: 0, - left: 0, - }, - }); - - // Step 7: Upload the generated PDF to storage - const result = await uploadFile({ - userId, - resumeId: id, - data: new Uint8Array(pdfBuffer), - contentType: "application/pdf", - type: "pdf", - }); - - return result.url; - } catch (error) { - throw new ORPCError("INTERNAL_SERVER_ERROR", error as Error); - } finally { - if (page) await page.close().catch(() => null); - } + activePrintJobs.set(id, job); + return job; }, + /** Captures a resume screenshot, deduplicating concurrent requests for the same resume. */ getResumeScreenshot: async ( input: Pick, "userId" | "id" | "data" | "updatedAt">, ): Promise => { - const { id, userId, data, updatedAt } = input; + const { id } = input; - const storageService = getStorageService(); - const screenshotPrefix = `uploads/${userId}/screenshots/${id}`; + // Deduplicate concurrent requests for the same resume + const existing = activeScreenshotJobs.get(id); + if (existing) return existing; - const existingScreenshots = await storageService.list(screenshotPrefix); - const now = Date.now(); - const resumeUpdatedAt = updatedAt.getTime(); + const job = doGetResumeScreenshot(input).finally(() => { + activeScreenshotJobs.delete(id); + }); - if (existingScreenshots.length > 0) { - const sortedFiles = existingScreenshots - .map((path) => { - const filename = path.split("/").pop(); - const match = filename?.match(/^(\d+)\.webp$/); - return match ? { path, timestamp: Number(match[1]) } : null; - }) - .filter((item): item is { path: string; timestamp: number } => item !== null) - .sort((a, b) => b.timestamp - a.timestamp); - - if (sortedFiles.length > 0) { - const latest = sortedFiles[0]; - const age = now - latest.timestamp; - - // Return existing screenshot if it's still fresh (within TTL) - if (age < SCREENSHOT_TTL) return new URL(latest.path, env.APP_URL).toString(); - - // Screenshot is stale (past TTL), but only regenerate if the resume - // was updated after the screenshot was taken. If the resume hasn't - // changed, keep using the existing screenshot to avoid unnecessary work. - if (resumeUpdatedAt <= latest.timestamp) { - return new URL(latest.path, env.APP_URL).toString(); - } - - // Resume was updated after the screenshot - delete old ones and regenerate - await Promise.all(sortedFiles.map((file) => storageService.delete(file.path))); - } - } - - const baseUrl = env.PRINTER_APP_URL ?? env.APP_URL; - const domain = new URL(baseUrl).hostname; - - const locale = data.metadata.page.locale; - - const token = generatePrinterToken(id); - const url = `${baseUrl}/printer/${id}?token=${token}`; - - let browser: Browser | null = null; - let page: Page | null = null; - - try { - browser = await getBrowser(); - - await browser.setCookie({ name: "locale", value: locale, domain }); - - page = await browser.newPage(); - - await page.setViewport(pageDimensionsAsPixels.a4); - await page.goto(url, { waitUntil: "networkidle0" }); - await page.waitForFunction(() => document.body.getAttribute("data-wf-loaded") === "true", { timeout: 5_000 }); - - const screenshotBuffer = await page.screenshot({ type: "webp", quality: 80 }); - - const result = await uploadFile({ - userId, - resumeId: id, - data: new Uint8Array(screenshotBuffer), - contentType: "image/webp", - type: "screenshot", - }); - - return result.url; - } catch (error) { - throw new ORPCError("INTERNAL_SERVER_ERROR", error as Error); - } finally { - if (page) await page.close().catch(() => null); - } + activeScreenshotJobs.set(id, job); + return job; }, }; diff --git a/src/integrations/orpc/services/resume.ts b/src/integrations/orpc/services/resume.ts index a144ce481..5e41f51eb 100644 --- a/src/integrations/orpc/services/resume.ts +++ b/src/integrations/orpc/services/resume.ts @@ -269,7 +269,8 @@ export const resumeService = { throw new ORPCError("RESUME_SLUG_ALREADY_EXISTS", { status: 400 }); } - throw error; + console.error("Failed to create resume:", error); + throw new ORPCError("INTERNAL_SERVER_ERROR", { message: "Failed to create resume" }); } }, @@ -325,7 +326,8 @@ export const resumeService = { throw new ORPCError("RESUME_SLUG_ALREADY_EXISTS", { status: 400 }); } - throw error; + console.error("Failed to update resume:", error); + throw new ORPCError("INTERNAL_SERVER_ERROR", { message: "Failed to update resume" }); } }, @@ -426,26 +428,23 @@ export const resumeService = { }, delete: async (input: { id: string; userId: string }) => { - const [resume] = await db - .select({ isLocked: schema.resume.isLocked }) - .from(schema.resume) - .where(and(eq(schema.resume.id, input.id), eq(schema.resume.userId, input.userId))); + await db.transaction(async (tx) => { + const [resume] = await tx + .select({ isLocked: schema.resume.isLocked }) + .from(schema.resume) + .where(and(eq(schema.resume.id, input.id), eq(schema.resume.userId, input.userId))); - if (!resume) throw new ORPCError("NOT_FOUND"); - if (resume.isLocked) throw new ORPCError("RESUME_LOCKED"); + if (!resume) throw new ORPCError("NOT_FOUND"); + if (resume.isLocked) throw new ORPCError("RESUME_LOCKED"); + await tx.delete(schema.resume).where(and(eq(schema.resume.id, input.id), eq(schema.resume.userId, input.userId))); + }); + + // Clean up storage files after the DB transaction succeeds const storageService = getStorageService(); - - const deleteResumePromise = db - .delete(schema.resume) - .where( - and(eq(schema.resume.id, input.id), eq(schema.resume.isLocked, false), eq(schema.resume.userId, input.userId)), - ); - - // Delete screenshots and PDFs using the new key format - const deleteScreenshotsPromise = storageService.delete(`uploads/${input.userId}/screenshots/${input.id}`); - const deletePdfsPromise = storageService.delete(`uploads/${input.userId}/pdfs/${input.id}`); - - await Promise.allSettled([deleteResumePromise, deleteScreenshotsPromise, deletePdfsPromise]); + await Promise.allSettled([ + storageService.delete(`uploads/${input.userId}/screenshots/${input.id}`), + storageService.delete(`uploads/${input.userId}/pdfs/${input.id}`), + ]); }, }; diff --git a/src/routes/_home/-sections/faq.tsx b/src/routes/_home/-sections/faq.tsx index f6f276128..797a24bce 100644 --- a/src/routes/_home/-sections/faq.tsx +++ b/src/routes/_home/-sections/faq.tsx @@ -70,14 +70,13 @@ export function FAQ() { > Frequently @@ -87,12 +86,11 @@ export function FAQ() { {faqItems.map((item, index) => ( @@ -112,12 +110,11 @@ type FAQItemComponentProps = { function FAQItemComponent({ item, index }: FAQItemComponentProps) { return ( {item.question} diff --git a/src/routes/_home/-sections/features.tsx b/src/routes/_home/-sections/features.tsx index 30c1d1dfd..e8080af18 100644 --- a/src/routes/_home/-sections/features.tsx +++ b/src/routes/_home/-sections/features.tsx @@ -136,7 +136,7 @@ function FeatureCard({ icon: Icon, title, description }: FeatureCardProps) { return ( {/* Hover gradient overlay */}
    {/* Header */}

    Features diff --git a/src/routes/_home/-sections/footer.tsx b/src/routes/_home/-sections/footer.tsx index 4798eb041..bc9e02a48 100644 --- a/src/routes/_home/-sections/footer.tsx +++ b/src/routes/_home/-sections/footer.tsx @@ -50,12 +50,11 @@ export function Footer() { return (
    {/* Brand Column */} @@ -143,8 +142,7 @@ function FooterLink({ url, label }: FooterLinkItem) { initial={{ width: 0, opacity: 0 }} animate={isHovered ? { width: "100%", opacity: 1 } : { width: 0, opacity: 0 }} transition={{ duration: 0.2, ease: "easeOut" }} - className="pointer-events-none absolute inset-s-0 -bottom-0.5 h-px rounded-md bg-primary" - style={{ willChange: "width, opacity" }} + className="pointer-events-none absolute inset-s-0 -bottom-0.5 h-px rounded-md bg-primary will-change-[width,opacity]" /> diff --git a/src/routes/_home/-sections/hero.tsx b/src/routes/_home/-sections/hero.tsx index f961613c2..e12799af3 100644 --- a/src/routes/_home/-sections/hero.tsx +++ b/src/routes/_home/-sections/hero.tsx @@ -18,10 +18,10 @@ export function Hero() {
    ); } diff --git a/src/routes/builder/$resumeId/-components/dock.tsx b/src/routes/builder/$resumeId/-components/dock.tsx index 9d4734905..ad2851945 100644 --- a/src/routes/builder/$resumeId/-components/dock.tsx +++ b/src/routes/builder/$resumeId/-components/dock.tsx @@ -111,8 +111,7 @@ export function BuilderDock() { animate={{ opacity: 0.6, y: 0 }} whileHover={{ opacity: 1, y: -2, scale: 1.01 }} transition={{ duration: 0.2, ease: "easeOut" }} - style={{ willChange: "transform, opacity" }} - className="flex items-center rounded-l-full rounded-r-full bg-popover px-2 shadow-xl" + className="flex items-center rounded-l-full rounded-r-full bg-popover px-2 shadow-xl will-change-[transform,opacity]" > - @@ -137,9 +131,10 @@ export function JobDetailSheet({ job, open, onOpenChange }: Props) {

    Description

    -

    - {job.job_description} -

    +
    )} @@ -197,7 +192,7 @@ export function JobDetailSheet({ job, open, onOpenChange }: Props) { )} - {job.apply_options.length > 0 && ( + {job.apply_options.some((option) => isValidExternalUrl(option.apply_link)) && ( <>
    @@ -205,23 +200,25 @@ export function JobDetailSheet({ job, open, onOpenChange }: Props) { Apply Via

    - {job.apply_options.map((option, i) => ( - - - {option.publisher || t`Apply Link`} - {option.is_direct && ( - - Direct - - )} - - ))} + {job.apply_options + .filter((option) => isValidExternalUrl(option.apply_link)) + .map((option, i) => ( + + + {option.publisher || t`Apply Link`} + {option.is_direct && ( + + Direct + + )} + + ))}
    diff --git a/src/routes/dashboard/job-search/-components/tailor-dialog.tsx b/src/routes/dashboard/job-search/-components/tailor-dialog.tsx index 6ee7a301d..5d3e3f83e 100644 --- a/src/routes/dashboard/job-search/-components/tailor-dialog.tsx +++ b/src/routes/dashboard/job-search/-components/tailor-dialog.tsx @@ -2,7 +2,6 @@ import { t } from "@lingui/core/macro"; import { Trans } from "@lingui/react/macro"; import { ReadCvLogoIcon } from "@phosphor-icons/react"; import { useMutation, useQuery } from "@tanstack/react-query"; -import { useNavigate } from "@tanstack/react-router"; import { useState } from "react"; import { toast } from "sonner"; @@ -40,7 +39,6 @@ type DialogPhase = | { step: "skill-sync"; newResumeId: string; newSkills: NewSkillInfo[]; sourceResumeId: string }; export function TailorDialog({ job, open, onOpenChange }: Props) { - const navigate = useNavigate(); const { data: resumes, isLoading } = useQuery(orpc.resume.list.queryOptions()); const [phase, setPhase] = useState({ step: "select" }); @@ -65,11 +63,7 @@ export function TailorDialog({ job, open, onOpenChange }: Props) { }; const navigateToBuilder = (resumeId: string) => { - if (job.job_apply_link) { - window.open(job.job_apply_link, "_blank", "noopener,noreferrer"); - } - handleOpenChange(false); - void navigate({ to: "/builder/$resumeId", params: { resumeId } }); + window.open(`/builder/${resumeId}`, "_blank", "noopener,noreferrer"); }; const handleSelectResume = async (resumeId: string, resumeName: string) => { diff --git a/src/routes/dashboard/job-search/index.tsx b/src/routes/dashboard/job-search/index.tsx index e2dc8abb6..9e5e731fb 100644 --- a/src/routes/dashboard/job-search/index.tsx +++ b/src/routes/dashboard/job-search/index.tsx @@ -2,14 +2,9 @@ import { t } from "@lingui/core/macro"; import { Trans } from "@lingui/react/macro"; import { BriefcaseIcon, - BuildingsIcon, CaretLeftIcon, CaretRightIcon, - ClockIcon, - GlobeIcon, MagnifyingGlassIcon, - MapPinIcon, - MoneyIcon, WarningCircleIcon, XIcon, } from "@phosphor-icons/react"; @@ -17,19 +12,19 @@ import { createFileRoute, Link } from "@tanstack/react-router"; import { motion } from "motion/react"; import { useMemo } from "react"; -import type { JobResult } from "@/schema/jobs"; - import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Separator } from "@/components/ui/separator"; +import { Skeleton } from "@/components/ui/skeleton"; import { Spinner } from "@/components/ui/spinner"; import { cn } from "@/utils/style"; import { DashboardHeader } from "../-components/header"; +import { JobCard } from "./-components/job-card"; import { JobDetailSheet } from "./-components/job-detail"; -import { formatPostedDate, formatSalary, getQuotaStatus } from "./-components/job-utils"; +import { getQuotaStatus } from "./-components/job-utils"; import { hasActiveFilters, initialFilterState, SearchFilters } from "./-components/search-filters"; import { useJobSearch } from "./-components/use-job-search"; @@ -37,75 +32,6 @@ export const Route = createFileRoute("/dashboard/job-search/")({ component: RouteComponent, }); -function JobCard({ job, onClick }: { job: JobResult; onClick: () => void }) { - const salary = formatSalary(job.job_min_salary, job.job_max_salary, job.job_salary_currency, job.job_salary_period); - const posted = formatPostedDate(job.job_posted_at_timestamp); - const location = [job.job_city, job.job_state, job.job_country].filter(Boolean).join(", "); - - return ( - -
    - {job.employer_logo ? ( - {job.employer_name} - ) : ( -
    - -
    - )} - -
    -

    {job.job_title}

    -

    {job.employer_name}

    -
    -
    - -
    - {location && ( - - - {location} - - )} - - {job.job_is_remote && ( - - - Remote - - )} - - {job.job_employment_type && ( - - - {job.job_employment_type.replaceAll("_", " ")} - - )} - - {salary && ( - - - {salary} - - )} - - {posted && ( - - - {posted} - - )} -
    -
    - ); -} - function RouteComponent() { const { activeFilterChips, @@ -144,7 +70,7 @@ function RouteComponent() {

    @@ -230,10 +156,19 @@ function RouteComponent() { {isPending && jobs.length === 0 && (
    {Array.from({ length: 6 }).map((_, index) => ( -
    -
    -
    -
    +
    +
    + +
    + + +
    +
    +
    + + + +
    ))}
    diff --git a/src/routes/dashboard/resumes/-components/cards/resume-card.tsx b/src/routes/dashboard/resumes/-components/cards/resume-card.tsx index e21d597f8..a242e3d76 100644 --- a/src/routes/dashboard/resumes/-components/cards/resume-card.tsx +++ b/src/routes/dashboard/resumes/-components/cards/resume-card.tsx @@ -32,10 +32,10 @@ export function ResumeCard({ resume }: ResumeCardProps) { {match({ isLoading, imageSrc: screenshotData?.url }) @@ -71,8 +71,7 @@ function ResumeLockOverlay({ isLocked }: { isLocked: boolean }) { animate={{ opacity: 0.6 }} exit={{ opacity: 0 }} transition={{ duration: 0.15 }} - style={{ willChange: "opacity" }} - className="absolute inset-0 flex items-center justify-center" + className="absolute inset-0 flex items-center justify-center will-change-[opacity]" >
    diff --git a/src/routes/dashboard/resumes/-components/grid-view.tsx b/src/routes/dashboard/resumes/-components/grid-view.tsx index 5621b7774..8b2970f0b 100644 --- a/src/routes/dashboard/resumes/-components/grid-view.tsx +++ b/src/routes/dashboard/resumes/-components/grid-view.tsx @@ -20,7 +20,7 @@ export function GridView({ resumes }: Props) { animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: -20 }} transition={{ duration: 0.2, ease: "easeOut" }} - style={{ willChange: "transform, opacity" }} + className="will-change-[transform,opacity]" > @@ -30,7 +30,7 @@ export function GridView({ resumes }: Props) { animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: -20 }} transition={{ duration: 0.2, delay: 0.03, ease: "easeOut" }} - style={{ willChange: "transform, opacity" }} + className="will-change-[transform,opacity]" > @@ -48,7 +48,7 @@ export function GridView({ resumes }: Props) { filter: "blur(8px)", }} transition={{ duration: 0.2, delay: Math.min(0.12, (index + 2) * 0.02), ease: "easeOut" }} - style={{ willChange: "transform, opacity" }} + className="will-change-[transform,opacity]" > diff --git a/src/routes/dashboard/resumes/-components/list-view.tsx b/src/routes/dashboard/resumes/-components/list-view.tsx index 88a22e70c..13b625e59 100644 --- a/src/routes/dashboard/resumes/-components/list-view.tsx +++ b/src/routes/dashboard/resumes/-components/list-view.tsx @@ -32,11 +32,11 @@ export function ListView({ resumes }: Props) { return (
    diff --git a/src/routes/dashboard/settings/danger-zone.tsx b/src/routes/dashboard/settings/danger-zone.tsx index 1da7efa56..fed4d927e 100644 --- a/src/routes/dashboard/settings/danger-zone.tsx +++ b/src/routes/dashboard/settings/danger-zone.tsx @@ -63,8 +63,7 @@ function RouteComponent() { initial={{ opacity: 0, y: -20 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.25, ease: "easeOut" }} - style={{ willChange: "transform, opacity" }} - className="grid max-w-xl gap-6" + className="grid max-w-xl gap-6 will-change-[transform,opacity]" >

    To delete your account, you need to enter the confirmation text and click the button below. @@ -78,11 +77,10 @@ function RouteComponent() { />