From c36b77086e3509f2b5bf30fab2be386afc722b96 Mon Sep 17 00:00:00 2001
From: jslyonnais <6282845+jslyonnais@users.noreply.github.com>
Date: Fri, 20 Feb 2026 10:19:35 -0500
Subject: [PATCH 11/19] change encryption method to mnemonic phrase
---
package-lock.json | 35 +++++++++++++++++
package.json | 1 +
src/__tests/auth.test.ts | 38 ++++++++++++++++++
src/lib/crypto.ts | 8 ++--
src/tests/auth.test.ts | 84 ----------------------------------------
5 files changed, 79 insertions(+), 87 deletions(-)
create mode 100644 src/__tests/auth.test.ts
delete mode 100644 src/tests/auth.test.ts
diff --git a/package-lock.json b/package-lock.json
index 93a74a7..3687cc3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,6 +8,7 @@
"dependencies": {
"@clerk/clerk-react": "^5.49.0",
"@convex-dev/react-query": "0.1.0",
+ "@scure/bip39": "^2.0.1",
"@t3-oss/env-core": "^0.13.8",
"@tailwindcss/vite": "^4.1.18",
"@tanstack/react-devtools": "^0.7.0",
@@ -1541,6 +1542,18 @@
"url": "https://github.com/sponsors/Brooooooklyn"
}
},
+ "node_modules/@noble/hashes": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.0.1.tgz",
+ "integrity": "sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 20.19.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
"node_modules/@oozcitak/dom": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@oozcitak/dom/-/dom-2.0.2.tgz",
@@ -3648,6 +3661,28 @@
"win32"
]
},
+ "node_modules/@scure/base": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@scure/base/-/base-2.0.0.tgz",
+ "integrity": "sha512-3E1kpuZginKkek01ovG8krQ0Z44E3DHPjc5S2rjJw9lZn3KSQOs8S7wqikF/AH7iRanHypj85uGyxk0XAyC37w==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/@scure/bip39": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-2.0.1.tgz",
+ "integrity": "sha512-PsxdFj/d2AcJcZDX1FXN3dDgitDDTmwf78rKZq1a6c1P1Nan1X/Sxc7667zU3U+AN60g7SxxP0YCVw2H/hBycg==",
+ "license": "MIT",
+ "dependencies": {
+ "@noble/hashes": "2.0.1",
+ "@scure/base": "2.0.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
"node_modules/@sinclair/typebox": {
"version": "0.31.28",
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.31.28.tgz",
diff --git a/package.json b/package.json
index 34ef23a..7bfea0d 100644
--- a/package.json
+++ b/package.json
@@ -16,6 +16,7 @@
"dependencies": {
"@clerk/clerk-react": "^5.49.0",
"@convex-dev/react-query": "0.1.0",
+ "@scure/bip39": "^2.0.1",
"@t3-oss/env-core": "^0.13.8",
"@tailwindcss/vite": "^4.1.18",
"@tanstack/react-devtools": "^0.7.0",
diff --git a/src/__tests/auth.test.ts b/src/__tests/auth.test.ts
new file mode 100644
index 0000000..b12c86a
--- /dev/null
+++ b/src/__tests/auth.test.ts
@@ -0,0 +1,38 @@
+import { describe, expect, it, vi } from 'vitest'
+import { createSession } from '@/hooks/auth.helpers'
+
+describe('Authentication Helpers', () => {
+ describe('createSession', () => {
+ it('activates session and returns user when getUser yields value', async () => {
+ const mockSetActive = vi.fn().mockResolvedValue(undefined)
+ const mockUser = { id: 'user_123', emailAddresses: [] }
+ const mockGetUser = vi.fn().mockReturnValue(mockUser)
+ const sessionId = 'sess_xyz789'
+
+ const result = await createSession(sessionId, mockSetActive, mockGetUser)
+
+ expect(mockSetActive).toHaveBeenCalledTimes(1)
+ expect(mockSetActive).toHaveBeenCalledWith(
+ expect.objectContaining({ session: sessionId }),
+ )
+ expect(mockGetUser).toHaveBeenCalled()
+ expect(result).toBe(mockUser)
+ })
+
+ it('polls getUser until value is available', async () => {
+ const mockSetActive = vi.fn().mockResolvedValue(undefined)
+ const mockUser = { id: 'user_456' }
+ const mockGetUser = vi
+ .fn()
+ .mockReturnValueOnce(undefined)
+ .mockReturnValueOnce(null)
+ .mockReturnValueOnce(mockUser)
+ const sessionId = 'sess_abc'
+
+ const result = await createSession(sessionId, mockSetActive, mockGetUser)
+
+ expect(mockGetUser).toHaveBeenCalledTimes(3)
+ expect(result).toBe(mockUser)
+ })
+ })
+})
diff --git a/src/lib/crypto.ts b/src/lib/crypto.ts
index 15bbeba..a4ef5dd 100644
--- a/src/lib/crypto.ts
+++ b/src/lib/crypto.ts
@@ -1,11 +1,13 @@
import * as openpgp from 'openpgp'
+import { generateMnemonic } from '@scure/bip39'
+import { wordlist } from '@scure/bip39/wordlists/english.js'
/**
- * Create a random passphrase
- * @returns A random passphrase as a Uint8Array
+ * Create a passphrase as a BIP39 mnemonic phrase
+ * @returns A 12-word mnemonic phrase (128 bits of entropy)
*/
export const createPassphrase = (): string => {
- return crypto.randomUUID()
+ return generateMnemonic(wordlist, 128)
}
/**
diff --git a/src/tests/auth.test.ts b/src/tests/auth.test.ts
deleted file mode 100644
index 5539c76..0000000
--- a/src/tests/auth.test.ts
+++ /dev/null
@@ -1,84 +0,0 @@
-import { describe, expect, it, vi } from 'vitest'
-import { activateSession, createSession } from '@/hooks/auth.helpers'
-import { RoutesPath } from '@/types/routes'
-
-describe('Authentication Helpers', () => {
- describe('createSession', () => {
- it('calls setActive with session ID and redirects to home via beforeEmit', async () => {
- const mockSetActive = vi.fn().mockResolvedValue(undefined)
- const mockNavigate = vi.fn()
- const sessionId = 'sess_abc123'
- const result = { createdSessionId: sessionId }
-
- await createSession(result, mockSetActive, mockNavigate)
-
- expect(mockSetActive).toHaveBeenCalledTimes(1)
- expect(mockSetActive).toHaveBeenCalledWith(
- expect.objectContaining({
- session: sessionId,
- }),
- )
-
- const callArg = mockSetActive.mock.calls[0]?.[0]
- expect(callArg?.beforeEmit).toBeDefined()
- await callArg?.beforeEmit?.()
- expect(mockNavigate).toHaveBeenCalledWith({
- to: RoutesPath.HOME.toString(),
- })
- })
-
- it('handles null createdSessionId', async () => {
- const mockSetActive = vi.fn().mockResolvedValue(undefined)
- const mockNavigate = vi.fn()
- const result = { createdSessionId: null }
-
- await createSession(result, mockSetActive, mockNavigate)
-
- expect(mockSetActive).toHaveBeenCalledWith(
- expect.objectContaining({ session: null }),
- )
- })
- })
-
- describe('activateSession', () => {
- it('activates session and returns user when getUser yields value', async () => {
- const mockSetActive = vi.fn().mockResolvedValue(undefined)
- const mockUser = { id: 'user_123', emailAddresses: [] }
- const mockGetUser = vi.fn().mockReturnValue(mockUser)
- const sessionId = 'sess_xyz789'
-
- const result = await activateSession(
- sessionId,
- mockSetActive,
- mockGetUser,
- )
-
- expect(mockSetActive).toHaveBeenCalledTimes(1)
- expect(mockSetActive).toHaveBeenCalledWith(
- expect.objectContaining({ session: sessionId }),
- )
- expect(mockGetUser).toHaveBeenCalled()
- expect(result).toBe(mockUser)
- })
-
- it('polls getUser until value is available', async () => {
- const mockSetActive = vi.fn().mockResolvedValue(undefined)
- const mockUser = { id: 'user_456' }
- const mockGetUser = vi
- .fn()
- .mockReturnValueOnce(undefined)
- .mockReturnValueOnce(null)
- .mockReturnValueOnce(mockUser)
- const sessionId = 'sess_abc'
-
- const result = await activateSession(
- sessionId,
- mockSetActive,
- mockGetUser,
- )
-
- expect(mockGetUser).toHaveBeenCalledTimes(3)
- expect(result).toBe(mockUser)
- })
- })
-})
From 7dc4fa70647ceebc1dcabfaf3d41e74cdec52064 Mon Sep 17 00:00:00 2001
From: jslyonnais <6282845+jslyonnais@users.noreply.github.com>
Date: Fri, 20 Feb 2026 10:27:13 -0500
Subject: [PATCH 12/19] tests
---
package.json | 2 +-
src/{__tests => __tests__}/auth.test.ts | 0
src/__tests__/crypto.test.ts | 55 +++++++++
src/__tests__/useEncryption.test.tsx | 137 +++++++++++++++++++++
src/__tests__/useSignIn.test.tsx | 140 ++++++++++++++++++++++
src/__tests__/useSignOut.test.tsx | 32 +++++
src/__tests__/useSignUp.test.tsx | 153 ++++++++++++++++++++++++
src/__tests__/waitFor.test.ts | 39 ++++++
vitest.config.ts | 3 +
9 files changed, 560 insertions(+), 1 deletion(-)
rename src/{__tests => __tests__}/auth.test.ts (100%)
create mode 100644 src/__tests__/crypto.test.ts
create mode 100644 src/__tests__/useEncryption.test.tsx
create mode 100644 src/__tests__/useSignIn.test.tsx
create mode 100644 src/__tests__/useSignOut.test.tsx
create mode 100644 src/__tests__/useSignUp.test.tsx
create mode 100644 src/__tests__/waitFor.test.ts
diff --git a/package.json b/package.json
index 7bfea0d..30b1092 100644
--- a/package.json
+++ b/package.json
@@ -6,7 +6,7 @@
"dev": "vite dev --port 3666",
"build": "vite build",
"preview": "vite preview",
- "test": "vitest run",
+ "test": "NODE_OPTIONS='--no-webstorage' vitest run",
"lint": "eslint",
"format": "prettier --check .",
"validate": "prettier --write . && eslint --fix",
diff --git a/src/__tests/auth.test.ts b/src/__tests__/auth.test.ts
similarity index 100%
rename from src/__tests/auth.test.ts
rename to src/__tests__/auth.test.ts
diff --git a/src/__tests__/crypto.test.ts b/src/__tests__/crypto.test.ts
new file mode 100644
index 0000000..8ca3f2a
--- /dev/null
+++ b/src/__tests__/crypto.test.ts
@@ -0,0 +1,55 @@
+/**
+ * @vitest-environment node
+ * OpenPGP has Uint8Array/instanceof issues in jsdom - run in Node
+ */
+import { describe, expect, it } from 'vitest'
+import {
+ createPassphrase,
+ decryptPassphrase,
+ encryptPassphrase,
+} from '@/lib/crypto'
+
+describe('Crypto', () => {
+ describe('createPassphrase', () => {
+ it('returns a 12-word BIP39 mnemonic phrase', () => {
+ const passphrase = createPassphrase()
+ const words = passphrase.trim().split(/\s+/)
+ expect(words).toHaveLength(12)
+ expect(passphrase).toMatch(/^[a-z]+(\s+[a-z]+){11}$/)
+ })
+
+ it('returns different passphrases on each call', () => {
+ const p1 = createPassphrase()
+ const p2 = createPassphrase()
+ expect(p1).not.toBe(p2)
+ })
+ })
+
+ describe('encryptPassphrase / decryptPassphrase', () => {
+ it('round-trips passphrase with correct password', async () => {
+ const passphrase = createPassphrase()
+ const password = 'mySecurePassword123'
+
+ const encrypted = await encryptPassphrase(passphrase, password)
+ expect(encrypted).toBeTruthy()
+ expect(encrypted).toContain('-----BEGIN PGP MESSAGE-----')
+
+ const decrypted = await decryptPassphrase(encrypted, password)
+ expect(decrypted).toBe(passphrase)
+ })
+
+ it('throws when decrying with wrong password', async () => {
+ const passphrase = 'test passphrase'
+ const encrypted = await encryptPassphrase(passphrase, 'correct')
+
+ await expect(decryptPassphrase(encrypted, 'wrong')).rejects.toThrow()
+ })
+
+ it('handles empty passphrase', async () => {
+ const encrypted = await encryptPassphrase('', 'password')
+ expect(encrypted).toBeTruthy()
+ const decrypted = await decryptPassphrase(encrypted, 'password')
+ expect(decrypted).toBe('')
+ })
+ })
+})
diff --git a/src/__tests__/useEncryption.test.tsx b/src/__tests__/useEncryption.test.tsx
new file mode 100644
index 0000000..0cfe921
--- /dev/null
+++ b/src/__tests__/useEncryption.test.tsx
@@ -0,0 +1,137 @@
+import { beforeEach, describe, expect, it, vi } from 'vitest'
+import { renderHook } from '@testing-library/react'
+import { useEncryption } from '@/hooks/useEncryption'
+
+const mockGenerateKey = vi.fn()
+const mockCreateMessage = vi.fn()
+const mockReadKey = vi.fn()
+const mockReadMessage = vi.fn()
+const mockReadPrivateKey = vi.fn()
+const mockDecryptKey = vi.fn()
+const mockEncrypt = vi.fn()
+const mockDecrypt = vi.fn()
+
+vi.mock('openpgp', () => ({
+ generateKey: (...args: Array
) => mockGenerateKey(...args),
+ createMessage: (...args: Array) => mockCreateMessage(...args),
+ readKey: (...args: Array) => mockReadKey(...args),
+ readMessage: (...args: Array) => mockReadMessage(...args),
+ readPrivateKey: (...args: Array) => mockReadPrivateKey(...args),
+ decryptKey: (...args: Array) => mockDecryptKey(...args),
+ encrypt: (...args: Array) => mockEncrypt(...args),
+ decrypt: (...args: Array) => mockDecrypt(...args),
+}))
+
+const mockUserKeys = vi.fn()
+vi.mock('convex/react', () => ({
+ useQuery: () => mockUserKeys(),
+}))
+
+describe('useEncryption', () => {
+ beforeEach(() => {
+ vi.clearAllMocks()
+ })
+
+ describe('generateKeyPair', () => {
+ it('returns PGP key pair for given email and passphrase', async () => {
+ mockUserKeys.mockReturnValue(null)
+ mockGenerateKey.mockResolvedValue({
+ privateKey: '-----BEGIN PGP PRIVATE KEY-----',
+ publicKey: '-----BEGIN PGP PUBLIC KEY-----',
+ })
+
+ const { result } = renderHook(() => useEncryption())
+
+ const { privateKey, publicKey } = await result.current.generateKeyPair(
+ 'test@example.com',
+ 'my passphrase',
+ )
+
+ expect(mockGenerateKey).toHaveBeenCalledWith(
+ expect.objectContaining({
+ type: 'ecc',
+ curve: 'nistP256',
+ userIDs: [{ email: 'test@example.com' }],
+ passphrase: 'my passphrase',
+ format: 'armored',
+ }),
+ )
+ expect(privateKey).toContain('-----BEGIN PGP PRIVATE KEY-----')
+ expect(publicKey).toContain('-----BEGIN PGP PUBLIC KEY-----')
+ })
+ })
+
+ describe('encryptData / decryptData', () => {
+ it('encrypts data when userKeys are available', async () => {
+ const mockPubKey = { type: 'public' }
+ mockUserKeys.mockReturnValue({
+ publicKey: 'armored-pub-key',
+ encryptedPrivateKey: 'armored-priv-key',
+ })
+ mockReadKey.mockResolvedValue(mockPubKey)
+ mockCreateMessage.mockResolvedValue({ message: 'msg' })
+ mockEncrypt.mockResolvedValue('-----BEGIN PGP MESSAGE-----')
+
+ const { result } = renderHook(() => useEncryption())
+
+ const encrypted = await result.current.encryptData('Secret message')
+ expect(encrypted).toContain('-----BEGIN PGP MESSAGE-----')
+ expect(mockReadKey).toHaveBeenCalledWith({
+ armoredKey: 'armored-pub-key',
+ })
+ })
+
+ it('decrypts data when userKeys and passphrase are available', async () => {
+ const mockPrivKey = { type: 'private' }
+ mockUserKeys.mockReturnValue({
+ publicKey: 'armored-pub-key',
+ encryptedPrivateKey: 'armored-priv-key',
+ })
+ mockReadMessage.mockResolvedValue({ msg: 'enc' })
+ mockReadPrivateKey.mockResolvedValue({ key: 'encrypted' })
+ mockDecryptKey.mockResolvedValue(mockPrivKey)
+ mockDecrypt.mockResolvedValue({ data: 'decrypted plaintext' })
+
+ const { result } = renderHook(() => useEncryption())
+
+ const decrypted = await result.current.decryptData(
+ 'encrypted-data',
+ 'passphrase',
+ )
+ expect(decrypted).toBe('decrypted plaintext')
+ })
+
+ it('throws when encryptData called with no userKeys', async () => {
+ mockUserKeys.mockReturnValue(null)
+
+ const { result } = renderHook(() => useEncryption())
+
+ await expect(result.current.encryptData('data')).rejects.toThrow(
+ 'No public key available',
+ )
+ })
+
+ it('throws when decryptData called with no userKeys', async () => {
+ mockUserKeys.mockReturnValue(null)
+
+ const { result } = renderHook(() => useEncryption())
+
+ await expect(
+ result.current.decryptData('encrypted', 'pass'),
+ ).rejects.toThrow('No public key available')
+ })
+
+ it('throws when decryptData called with empty passphrase', async () => {
+ mockUserKeys.mockReturnValue({
+ publicKey: 'pub',
+ encryptedPrivateKey: 'priv',
+ })
+
+ const { result } = renderHook(() => useEncryption())
+
+ await expect(result.current.decryptData('encrypted', '')).rejects.toThrow(
+ 'No passphrase available',
+ )
+ })
+ })
+})
diff --git a/src/__tests__/useSignIn.test.tsx b/src/__tests__/useSignIn.test.tsx
new file mode 100644
index 0000000..3ff1e49
--- /dev/null
+++ b/src/__tests__/useSignIn.test.tsx
@@ -0,0 +1,140 @@
+import { beforeEach, describe, expect, it, vi } from 'vitest'
+import { act, renderHook } from '@testing-library/react'
+import { useSignIn } from '@/hooks/useSignIn'
+
+const mockSignInCreate = vi.fn()
+const mockSetActive = vi.fn()
+const mockRouter = vi.fn()
+const mockSetPassphrase = vi.fn()
+const mockUserKeys = vi.fn()
+
+vi.mock('@clerk/clerk-react', () => ({
+ useClerk: () => ({ user: { id: 'user_1' } }),
+ useSignIn: () => ({
+ isLoaded: true,
+ signIn: {
+ create: mockSignInCreate,
+ prepareSecondFactor: vi.fn(),
+ attemptSecondFactor: vi.fn(),
+ },
+ setActive: mockSetActive,
+ }),
+}))
+
+vi.mock('convex/react', () => ({
+ useQuery: () => mockUserKeys(),
+}))
+
+vi.mock('jotai', () => ({
+ useAtom: () => [null, mockSetPassphrase],
+}))
+
+vi.mock('@tanstack/react-router', () => ({
+ useNavigate: () => mockRouter,
+}))
+
+vi.mock('@/lib/crypto', () => ({
+ decryptPassphrase: vi.fn().mockResolvedValue('decrypted-passphrase'),
+}))
+
+describe('useSignIn', () => {
+ beforeEach(() => {
+ vi.clearAllMocks()
+ mockSignInCreate.mockReset()
+ mockSetActive.mockReset()
+ })
+
+ it('exposes form state and handlers', () => {
+ mockUserKeys.mockReturnValue(null)
+ const { result } = renderHook(() => useSignIn())
+
+ expect(result.current.email).toBe('')
+ expect(result.current.password).toBe('')
+ expect(result.current.code).toBe('')
+ expect(result.current.isLoading).toBe(false)
+ expect(result.current.requiresMfa).toBe(false)
+ expect(result.current.error).toBe(null)
+ expect(typeof result.current.handleSignIn).toBe('function')
+ expect(typeof result.current.handleMfaVerify).toBe('function')
+ })
+
+ it('updates email and password via setters', () => {
+ mockUserKeys.mockReturnValue(null)
+ const { result } = renderHook(() => useSignIn())
+
+ act(() => {
+ result.current.setEmail('test@example.com')
+ result.current.setPassword('secret123')
+ })
+
+ expect(result.current.email).toBe('test@example.com')
+ expect(result.current.password).toBe('secret123')
+ })
+
+ it('handles sign-in success and redirects', async () => {
+ mockSignInCreate.mockResolvedValue({
+ status: 'complete',
+ createdSessionId: 'sess_123',
+ })
+ mockSetActive.mockResolvedValue(undefined)
+ mockUserKeys.mockReturnValue({
+ encryptedPassphrase: 'encrypted',
+ })
+
+ const { result } = renderHook(() => useSignIn())
+
+ act(() => {
+ result.current.setEmail('test@example.com')
+ result.current.setPassword('password')
+ })
+
+ await act(async () => {
+ result.current.handleSignIn({
+ preventDefault: vi.fn(),
+ } as unknown as React.SubmitEvent)
+ })
+
+ expect(mockSignInCreate).toHaveBeenCalledWith({
+ strategy: 'password',
+ identifier: 'test@example.com',
+ password: 'password',
+ })
+ expect(mockSetActive).toHaveBeenCalled()
+ expect(mockSetPassphrase).toHaveBeenCalledWith('decrypted-passphrase')
+ expect(mockRouter).toHaveBeenCalled()
+ })
+
+ it('sets error on sign-in failure', async () => {
+ mockSignInCreate.mockRejectedValue(new Error('Invalid credentials'))
+ mockUserKeys.mockReturnValue(null)
+
+ const { result } = renderHook(() => useSignIn())
+
+ act(() => {
+ result.current.setEmail('test@example.com')
+ result.current.setPassword('wrong')
+ })
+
+ await act(async () => {
+ result.current.handleSignIn({
+ preventDefault: vi.fn(),
+ } as unknown as React.SubmitEvent)
+ })
+
+ expect(result.current.error).toBe('Invalid credentials')
+ expect(mockRouter).not.toHaveBeenCalled()
+ })
+
+ it('does nothing when email is empty', async () => {
+ mockUserKeys.mockReturnValue(null)
+ const { result } = renderHook(() => useSignIn())
+
+ await act(async () => {
+ result.current.handleSignIn({
+ preventDefault: vi.fn(),
+ } as unknown as React.SubmitEvent)
+ })
+
+ expect(mockSignInCreate).not.toHaveBeenCalled()
+ })
+})
diff --git a/src/__tests__/useSignOut.test.tsx b/src/__tests__/useSignOut.test.tsx
new file mode 100644
index 0000000..42e597d
--- /dev/null
+++ b/src/__tests__/useSignOut.test.tsx
@@ -0,0 +1,32 @@
+import { beforeEach, describe, expect, it, vi } from 'vitest'
+import { act, renderHook } from '@testing-library/react'
+import { RESET } from 'jotai/utils'
+import { useSignOut } from '@/hooks/useSignOut'
+
+const mockSignOut = vi.fn().mockResolvedValue(undefined)
+const mockSetPassphrase = vi.fn()
+
+vi.mock('@clerk/clerk-react', () => ({
+ useClerk: () => ({ signOut: mockSignOut }),
+}))
+
+vi.mock('jotai', () => ({
+ useAtom: () => [null, mockSetPassphrase],
+}))
+
+describe('useSignOut', () => {
+ beforeEach(() => {
+ vi.clearAllMocks()
+ })
+
+ it('resets passphrase and signs out on handleSignOut', async () => {
+ const { result } = renderHook(() => useSignOut())
+
+ await act(async () => {
+ await result.current.handleSignOut()
+ })
+
+ expect(mockSetPassphrase).toHaveBeenCalledWith(RESET)
+ expect(mockSignOut).toHaveBeenCalledTimes(1)
+ })
+})
diff --git a/src/__tests__/useSignUp.test.tsx b/src/__tests__/useSignUp.test.tsx
new file mode 100644
index 0000000..a74b5e9
--- /dev/null
+++ b/src/__tests__/useSignUp.test.tsx
@@ -0,0 +1,153 @@
+import { beforeEach, describe, expect, it, vi } from 'vitest'
+import { act, renderHook } from '@testing-library/react'
+import { useSignUp } from '@/hooks/useSignUp'
+
+const mockSetActive = vi.fn()
+const mockNavigate = vi.fn()
+const mockCreateUserKeys = vi.fn()
+const mockSetPassphrase = vi.fn()
+const mockGenerateKeyPair = vi.fn()
+
+const mockSignUpCreate = vi.fn()
+const mockPrepareEmailVerification = vi.fn()
+const mockAttemptEmailVerification = vi.fn()
+
+vi.mock('@clerk/clerk-react', () => ({
+ useClerk: () => ({ user: { id: 'user_1' } }),
+ useSignUp: () => ({
+ isLoaded: true,
+ signUp: {
+ create: mockSignUpCreate,
+ prepareEmailAddressVerification: mockPrepareEmailVerification,
+ attemptEmailAddressVerification: mockAttemptEmailVerification,
+ },
+ setActive: mockSetActive,
+ }),
+}))
+
+vi.mock('convex/react', () => ({
+ useMutation: () => mockCreateUserKeys,
+}))
+
+vi.mock('@/hooks/useEncryption', () => ({
+ useEncryption: () => ({
+ generateKeyPair: mockGenerateKeyPair,
+ }),
+}))
+
+vi.mock('jotai', () => ({
+ useAtom: () => [null, mockSetPassphrase],
+}))
+
+vi.mock('@tanstack/react-router', () => ({
+ useNavigate: () => mockNavigate,
+}))
+
+vi.mock('@/lib/crypto', () => ({
+ createPassphrase: vi.fn().mockReturnValue('mnemonic passphrase'),
+ encryptPassphrase: vi.fn().mockResolvedValue('encrypted-passphrase'),
+}))
+
+describe('useSignUp', () => {
+ beforeEach(() => {
+ vi.clearAllMocks()
+ mockCreateUserKeys.mockResolvedValue(undefined)
+ mockGenerateKeyPair.mockResolvedValue({
+ privateKey: '-----BEGIN PGP PRIVATE KEY-----',
+ publicKey: '-----BEGIN PGP PUBLIC KEY-----',
+ })
+ })
+
+ it('exposes form state and handlers', () => {
+ const { result } = renderHook(() => useSignUp())
+
+ expect(result.current.email).toBe('')
+ expect(result.current.password).toBe('')
+ expect(result.current.confirmPassword).toBe('')
+ expect(result.current.verificationCode).toBe('')
+ expect(result.current.isLoading).toBe(false)
+ expect(result.current.pendingVerification).toBe(false)
+ expect(typeof result.current.handleSignUp).toBe('function')
+ expect(typeof result.current.handleVerification).toBe('function')
+ })
+
+ it('creates account and sets pendingVerification on handleSignUp', async () => {
+ mockSignUpCreate.mockResolvedValue({
+ status: 'missing_requirements',
+ })
+ mockPrepareEmailVerification.mockResolvedValue(undefined)
+
+ const { result } = renderHook(() => useSignUp())
+
+ act(() => {
+ result.current.setEmail('new@example.com')
+ result.current.setPassword('password123')
+ })
+
+ await act(async () => {
+ result.current.handleSignUp({
+ preventDefault: vi.fn(),
+ } as unknown as React.SubmitEvent)
+ })
+
+ expect(mockSignUpCreate).toHaveBeenCalledWith({
+ emailAddress: 'new@example.com',
+ password: 'password123',
+ })
+ expect(result.current.pendingVerification).toBe(true)
+ })
+
+ it('verifies email and creates keys on handleVerification', async () => {
+ mockAttemptEmailVerification.mockResolvedValue({
+ status: 'complete',
+ createdSessionId: 'sess_456',
+ })
+ mockSetActive.mockResolvedValue(undefined)
+
+ const { result } = renderHook(() => useSignUp())
+
+ act(() => {
+ result.current.setEmail('verify@example.com')
+ result.current.setPassword('pass')
+ result.current.setVerificationCode('123456')
+ })
+
+ await act(async () => {
+ result.current.handleVerification({
+ preventDefault: vi.fn(),
+ } as unknown as React.SubmitEvent)
+ })
+
+ expect(mockAttemptEmailVerification).toHaveBeenCalledWith({
+ code: '123456',
+ })
+ expect(mockCreateUserKeys).toHaveBeenCalledWith(
+ expect.objectContaining({
+ encryptedPassphrase: 'encrypted-passphrase',
+ encryptedPrivateKey: expect.any(String),
+ publicKey: expect.any(String),
+ }),
+ )
+ expect(mockSetPassphrase).toHaveBeenCalledWith('mnemonic passphrase')
+ expect(mockNavigate).toHaveBeenCalled()
+ })
+
+ it('sets error on sign-up failure', async () => {
+ mockSignUpCreate.mockRejectedValue(new Error('Email taken'))
+
+ const { result } = renderHook(() => useSignUp())
+
+ act(() => {
+ result.current.setEmail('taken@example.com')
+ result.current.setPassword('pass')
+ })
+
+ await act(async () => {
+ result.current.handleSignUp({
+ preventDefault: vi.fn(),
+ } as unknown as React.SubmitEvent)
+ })
+
+ expect(result.current.error).toBe('Email taken')
+ })
+})
diff --git a/src/__tests__/waitFor.test.ts b/src/__tests__/waitFor.test.ts
new file mode 100644
index 0000000..5bc1cf3
--- /dev/null
+++ b/src/__tests__/waitFor.test.ts
@@ -0,0 +1,39 @@
+import { describe, expect, it, vi } from 'vitest'
+import { waitForValue } from '@/lib/waitFor'
+
+describe('waitForValue', () => {
+ it('returns value immediately when available', async () => {
+ const getValue = vi.fn().mockReturnValue('ready')
+ const result = await waitForValue(getValue)
+ expect(result).toBe('ready')
+ expect(getValue).toHaveBeenCalledTimes(1)
+ })
+
+ it('polls until value is available', async () => {
+ const getValue = vi
+ .fn()
+ .mockReturnValueOnce(undefined)
+ .mockReturnValueOnce(undefined)
+ .mockReturnValueOnce(42)
+ const result = await waitForValue(getValue, { pollInterval: 5 })
+ expect(result).toBe(42)
+ expect(getValue).toHaveBeenCalledTimes(3)
+ })
+
+ it('calls onRetry callback on each poll', async () => {
+ const onRetry = vi.fn()
+ const getValue = vi
+ .fn()
+ .mockReturnValueOnce(undefined)
+ .mockReturnValueOnce('done')
+ await waitForValue(getValue, { pollInterval: 5, onRetry })
+ expect(onRetry).toHaveBeenCalledTimes(1)
+ })
+
+ it('throws after timeout when value never appears', async () => {
+ const getValue = vi.fn().mockReturnValue(undefined)
+ await expect(
+ waitForValue(getValue, { timeout: 50, pollInterval: 10 }),
+ ).rejects.toThrow(/Timeout waiting for value/)
+ })
+})
diff --git a/vitest.config.ts b/vitest.config.ts
index edfee43..90843c1 100644
--- a/vitest.config.ts
+++ b/vitest.config.ts
@@ -11,6 +11,9 @@ export default defineConfig({
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
+ '@convex/_generated/api': fileURLToPath(
+ new URL('./convex/_generated/api.js', import.meta.url),
+ ),
},
},
})
From f0b9bd9572fa52dcbee6b043547364d8183b49a4 Mon Sep 17 00:00:00 2001
From: jslyonnais <6282845+jslyonnais@users.noreply.github.com>
Date: Fri, 20 Feb 2026 10:32:57 -0500
Subject: [PATCH 13/19] tests refactor code + typo
---
src/__tests__/crypto.test.ts | 2 +-
src/__tests__/useSignIn.test.tsx | 70 ++++++++++++++++++++++++--------
2 files changed, 55 insertions(+), 17 deletions(-)
diff --git a/src/__tests__/crypto.test.ts b/src/__tests__/crypto.test.ts
index 8ca3f2a..76ac21d 100644
--- a/src/__tests__/crypto.test.ts
+++ b/src/__tests__/crypto.test.ts
@@ -38,7 +38,7 @@ describe('Crypto', () => {
expect(decrypted).toBe(passphrase)
})
- it('throws when decrying with wrong password', async () => {
+ it('throws when decrypting with wrong password', async () => {
const passphrase = 'test passphrase'
const encrypted = await encryptPassphrase(passphrase, 'correct')
diff --git a/src/__tests__/useSignIn.test.tsx b/src/__tests__/useSignIn.test.tsx
index 3ff1e49..62db751 100644
--- a/src/__tests__/useSignIn.test.tsx
+++ b/src/__tests__/useSignIn.test.tsx
@@ -3,6 +3,7 @@ import { act, renderHook } from '@testing-library/react'
import { useSignIn } from '@/hooks/useSignIn'
const mockSignInCreate = vi.fn()
+const mockAttemptSecondFactor = vi.fn()
const mockSetActive = vi.fn()
const mockRouter = vi.fn()
const mockSetPassphrase = vi.fn()
@@ -15,7 +16,7 @@ vi.mock('@clerk/clerk-react', () => ({
signIn: {
create: mockSignInCreate,
prepareSecondFactor: vi.fn(),
- attemptSecondFactor: vi.fn(),
+ attemptSecondFactor: mockAttemptSecondFactor,
},
setActive: mockSetActive,
}),
@@ -40,8 +41,6 @@ vi.mock('@/lib/crypto', () => ({
describe('useSignIn', () => {
beforeEach(() => {
vi.clearAllMocks()
- mockSignInCreate.mockReset()
- mockSetActive.mockReset()
})
it('exposes form state and handlers', () => {
@@ -58,19 +57,6 @@ describe('useSignIn', () => {
expect(typeof result.current.handleMfaVerify).toBe('function')
})
- it('updates email and password via setters', () => {
- mockUserKeys.mockReturnValue(null)
- const { result } = renderHook(() => useSignIn())
-
- act(() => {
- result.current.setEmail('test@example.com')
- result.current.setPassword('secret123')
- })
-
- expect(result.current.email).toBe('test@example.com')
- expect(result.current.password).toBe('secret123')
- })
-
it('handles sign-in success and redirects', async () => {
mockSignInCreate.mockResolvedValue({
status: 'complete',
@@ -137,4 +123,56 @@ describe('useSignIn', () => {
expect(mockSignInCreate).not.toHaveBeenCalled()
})
+
+ it('handleMfaVerify verifies code, decrypts passphrase and redirects', async () => {
+ mockAttemptSecondFactor.mockResolvedValue({
+ status: 'complete',
+ createdSessionId: 'sess_mfa',
+ })
+ mockSetActive.mockResolvedValue(undefined)
+ mockUserKeys.mockReturnValue({
+ encryptedPassphrase: 'encrypted',
+ })
+
+ const { result } = renderHook(() => useSignIn())
+
+ act(() => {
+ result.current.setPassword('password')
+ result.current.setCode('654321')
+ })
+
+ await act(async () => {
+ result.current.handleMfaVerify({
+ preventDefault: vi.fn(),
+ } as unknown as React.SubmitEvent)
+ })
+
+ expect(mockAttemptSecondFactor).toHaveBeenCalledWith({
+ strategy: 'email_code',
+ code: '654321',
+ })
+ expect(mockSetActive).toHaveBeenCalled()
+ expect(mockSetPassphrase).toHaveBeenCalledWith('decrypted-passphrase')
+ expect(mockRouter).toHaveBeenCalled()
+ })
+
+ it('sets error on MFA verification failure', async () => {
+ mockAttemptSecondFactor.mockRejectedValue(new Error('Invalid code'))
+ mockUserKeys.mockReturnValue(null)
+
+ const { result } = renderHook(() => useSignIn())
+
+ act(() => {
+ result.current.setCode('000000')
+ })
+
+ await act(async () => {
+ result.current.handleMfaVerify({
+ preventDefault: vi.fn(),
+ } as unknown as React.SubmitEvent)
+ })
+
+ expect(result.current.error).toBe('Invalid code')
+ expect(mockRouter).not.toHaveBeenCalled()
+ })
})
From 69fd440ecac6f014be3d15429ea2ac9c13163c2d Mon Sep 17 00:00:00 2001
From: jslyonnais <6282845+jslyonnais@users.noreply.github.com>
Date: Fri, 20 Feb 2026 11:13:52 -0500
Subject: [PATCH 14/19] refactor to simplify the auth flow
---
package-lock.json | 32 ++++++--
package.json | 3 +-
src/__tests__/crypto.test.ts | 47 ++++++++++++
src/__tests__/useEncryption.test.tsx | 78 +++++--------------
src/__tests__/useSignIn.test.tsx | 29 ++++++-
src/__tests__/useSignUp.test.tsx | 33 ++++++--
src/hooks/auth.helpers.ts | 57 ++++++++++++++
src/hooks/useEncryption.ts | 57 +++-----------
src/hooks/useSignIn.ts | 111 +++++++++++++++------------
src/hooks/useSignUp.ts | 84 +++++++++++---------
src/lib/crypto.ts | 78 +++++++++++++++++++
src/types/auth.ts | 19 ++---
12 files changed, 408 insertions(+), 220 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 3687cc3..323bac7 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -23,7 +23,7 @@
"@tanstack/store": "^0.8.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
- "convex": "^1.27.3",
+ "convex": "^1.32.0",
"jotai": "^2.18.0",
"jotai-devtools": "^0.13.0",
"lucide-react": "^0.561.0",
@@ -6320,13 +6320,14 @@
"license": "MIT"
},
"node_modules/convex": {
- "version": "1.31.7",
- "resolved": "https://registry.npmjs.org/convex/-/convex-1.31.7.tgz",
- "integrity": "sha512-PtNMe1mAIOvA8Yz100QTOaIdgt2rIuWqencVXrb4McdhxBHZ8IJ1eXTnrgCC9HydyilGT1pOn+KNqT14mqn9fQ==",
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/convex/-/convex-1.32.0.tgz",
+ "integrity": "sha512-5FlajdLpW75pdLS+/CgGH5H6yeRuA+ru50AKJEYbJpmyILUS+7fdTvsdTaQ7ZFXMv0gE8mX4S+S3AtJ94k0mfw==",
"license": "Apache-2.0",
"dependencies": {
"esbuild": "0.27.0",
- "prettier": "^3.0.0"
+ "prettier": "^3.0.0",
+ "ws": "8.18.0"
},
"bin": {
"convex": "bin/main.js"
@@ -6352,6 +6353,27 @@
}
}
},
+ "node_modules/convex/node_modules/ws": {
+ "version": "8.18.0",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
+ "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": ">=5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ },
"node_modules/cookie-es": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-2.0.0.tgz",
diff --git a/package.json b/package.json
index 30b1092..64927f5 100644
--- a/package.json
+++ b/package.json
@@ -3,6 +3,7 @@
"private": true,
"type": "module",
"scripts": {
+ "start": "npm run db:start & npm run dev",
"dev": "vite dev --port 3666",
"build": "vite build",
"preview": "vite preview",
@@ -31,7 +32,7 @@
"@tanstack/store": "^0.8.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
- "convex": "^1.27.3",
+ "convex": "^1.32.0",
"jotai": "^2.18.0",
"jotai-devtools": "^0.13.0",
"lucide-react": "^0.561.0",
diff --git a/src/__tests__/crypto.test.ts b/src/__tests__/crypto.test.ts
index 76ac21d..0b3dcef 100644
--- a/src/__tests__/crypto.test.ts
+++ b/src/__tests__/crypto.test.ts
@@ -6,7 +6,10 @@ import { describe, expect, it } from 'vitest'
import {
createPassphrase,
decryptPassphrase,
+ decryptWithPrivateKey,
encryptPassphrase,
+ encryptWithPublicKey,
+ generateKeyPair,
} from '@/lib/crypto'
describe('Crypto', () => {
@@ -52,4 +55,48 @@ describe('Crypto', () => {
expect(decrypted).toBe('')
})
})
+
+ describe('generateKeyPair', () => {
+ it('returns armored PGP key pair for email and passphrase', async () => {
+ const { privateKey, publicKey } = await generateKeyPair(
+ 'test@example.com',
+ 'my passphrase',
+ )
+
+ expect(privateKey).toContain('-----BEGIN PGP PRIVATE KEY')
+ expect(publicKey).toContain('-----BEGIN PGP PUBLIC KEY')
+ })
+ })
+
+ describe('encryptWithPublicKey / decryptWithPrivateKey', () => {
+ it('round-trips data with a key pair', async () => {
+ const { privateKey, publicKey } = await generateKeyPair(
+ 'encrypt-test@example.com',
+ 'passphrase123',
+ )
+
+ const plaintext = 'Secret message to encrypt'
+ const encrypted = await encryptWithPublicKey(plaintext, publicKey)
+ expect(encrypted).toContain('-----BEGIN PGP MESSAGE-----')
+
+ const decrypted = await decryptWithPrivateKey(
+ encrypted,
+ privateKey,
+ 'passphrase123',
+ )
+ expect(decrypted).toBe(plaintext)
+ })
+
+ it('throws when decrypting with wrong passphrase', async () => {
+ const { privateKey, publicKey } = await generateKeyPair(
+ 'wrong-pass@example.com',
+ 'correct',
+ )
+ const encrypted = await encryptWithPublicKey('data', publicKey)
+
+ await expect(
+ decryptWithPrivateKey(encrypted, privateKey, 'wrong'),
+ ).rejects.toThrow()
+ })
+ })
})
diff --git a/src/__tests__/useEncryption.test.tsx b/src/__tests__/useEncryption.test.tsx
index 0cfe921..a4c0c55 100644
--- a/src/__tests__/useEncryption.test.tsx
+++ b/src/__tests__/useEncryption.test.tsx
@@ -2,24 +2,14 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
import { renderHook } from '@testing-library/react'
import { useEncryption } from '@/hooks/useEncryption'
-const mockGenerateKey = vi.fn()
-const mockCreateMessage = vi.fn()
-const mockReadKey = vi.fn()
-const mockReadMessage = vi.fn()
-const mockReadPrivateKey = vi.fn()
-const mockDecryptKey = vi.fn()
-const mockEncrypt = vi.fn()
-const mockDecrypt = vi.fn()
-
-vi.mock('openpgp', () => ({
- generateKey: (...args: Array) => mockGenerateKey(...args),
- createMessage: (...args: Array) => mockCreateMessage(...args),
- readKey: (...args: Array) => mockReadKey(...args),
- readMessage: (...args: Array) => mockReadMessage(...args),
- readPrivateKey: (...args: Array) => mockReadPrivateKey(...args),
- decryptKey: (...args: Array) => mockDecryptKey(...args),
- encrypt: (...args: Array) => mockEncrypt(...args),
- decrypt: (...args: Array) => mockDecrypt(...args),
+const mockEncryptWithPublicKey = vi.fn()
+const mockDecryptWithPrivateKey = vi.fn()
+
+vi.mock('@/lib/crypto', () => ({
+ encryptWithPublicKey: (...args: Array) =>
+ mockEncryptWithPublicKey(...args),
+ decryptWithPrivateKey: (...args: Array) =>
+ mockDecryptWithPrivateKey(...args),
}))
const mockUserKeys = vi.fn()
@@ -32,65 +22,30 @@ describe('useEncryption', () => {
vi.clearAllMocks()
})
- describe('generateKeyPair', () => {
- it('returns PGP key pair for given email and passphrase', async () => {
- mockUserKeys.mockReturnValue(null)
- mockGenerateKey.mockResolvedValue({
- privateKey: '-----BEGIN PGP PRIVATE KEY-----',
- publicKey: '-----BEGIN PGP PUBLIC KEY-----',
- })
-
- const { result } = renderHook(() => useEncryption())
-
- const { privateKey, publicKey } = await result.current.generateKeyPair(
- 'test@example.com',
- 'my passphrase',
- )
-
- expect(mockGenerateKey).toHaveBeenCalledWith(
- expect.objectContaining({
- type: 'ecc',
- curve: 'nistP256',
- userIDs: [{ email: 'test@example.com' }],
- passphrase: 'my passphrase',
- format: 'armored',
- }),
- )
- expect(privateKey).toContain('-----BEGIN PGP PRIVATE KEY-----')
- expect(publicKey).toContain('-----BEGIN PGP PUBLIC KEY-----')
- })
- })
-
describe('encryptData / decryptData', () => {
it('encrypts data when userKeys are available', async () => {
- const mockPubKey = { type: 'public' }
mockUserKeys.mockReturnValue({
publicKey: 'armored-pub-key',
encryptedPrivateKey: 'armored-priv-key',
})
- mockReadKey.mockResolvedValue(mockPubKey)
- mockCreateMessage.mockResolvedValue({ message: 'msg' })
- mockEncrypt.mockResolvedValue('-----BEGIN PGP MESSAGE-----')
+ mockEncryptWithPublicKey.mockResolvedValue('-----BEGIN PGP MESSAGE-----')
const { result } = renderHook(() => useEncryption())
const encrypted = await result.current.encryptData('Secret message')
expect(encrypted).toContain('-----BEGIN PGP MESSAGE-----')
- expect(mockReadKey).toHaveBeenCalledWith({
- armoredKey: 'armored-pub-key',
- })
+ expect(mockEncryptWithPublicKey).toHaveBeenCalledWith(
+ 'Secret message',
+ 'armored-pub-key',
+ )
})
it('decrypts data when userKeys and passphrase are available', async () => {
- const mockPrivKey = { type: 'private' }
mockUserKeys.mockReturnValue({
publicKey: 'armored-pub-key',
encryptedPrivateKey: 'armored-priv-key',
})
- mockReadMessage.mockResolvedValue({ msg: 'enc' })
- mockReadPrivateKey.mockResolvedValue({ key: 'encrypted' })
- mockDecryptKey.mockResolvedValue(mockPrivKey)
- mockDecrypt.mockResolvedValue({ data: 'decrypted plaintext' })
+ mockDecryptWithPrivateKey.mockResolvedValue('decrypted plaintext')
const { result } = renderHook(() => useEncryption())
@@ -99,6 +54,11 @@ describe('useEncryption', () => {
'passphrase',
)
expect(decrypted).toBe('decrypted plaintext')
+ expect(mockDecryptWithPrivateKey).toHaveBeenCalledWith(
+ 'encrypted-data',
+ 'armored-priv-key',
+ 'passphrase',
+ )
})
it('throws when encryptData called with no userKeys', async () => {
diff --git a/src/__tests__/useSignIn.test.tsx b/src/__tests__/useSignIn.test.tsx
index 62db751..63a277a 100644
--- a/src/__tests__/useSignIn.test.tsx
+++ b/src/__tests__/useSignIn.test.tsx
@@ -9,13 +9,15 @@ const mockRouter = vi.fn()
const mockSetPassphrase = vi.fn()
const mockUserKeys = vi.fn()
+const mockPrepareSecondFactor = vi.fn()
+
vi.mock('@clerk/clerk-react', () => ({
useClerk: () => ({ user: { id: 'user_1' } }),
useSignIn: () => ({
isLoaded: true,
signIn: {
create: mockSignInCreate,
- prepareSecondFactor: vi.fn(),
+ prepareSecondFactor: mockPrepareSecondFactor,
attemptSecondFactor: mockAttemptSecondFactor,
},
setActive: mockSetActive,
@@ -111,6 +113,31 @@ describe('useSignIn', () => {
expect(mockRouter).not.toHaveBeenCalled()
})
+ it('stops at MFA when needs_second_factor: no setupPassphrase or redirect', async () => {
+ mockSignInCreate.mockResolvedValue({
+ status: 'needs_second_factor',
+ supportedSecondFactors: [{ strategy: 'email_code' }],
+ })
+ mockUserKeys.mockReturnValue(null)
+
+ const { result } = renderHook(() => useSignIn())
+
+ act(() => {
+ result.current.setEmail('mfa@example.com')
+ result.current.setPassword('password')
+ })
+
+ await act(async () => {
+ result.current.handleSignIn({
+ preventDefault: vi.fn(),
+ } as unknown as React.SubmitEvent)
+ })
+
+ expect(result.current.requiresMfa).toBe(true)
+ expect(mockSetPassphrase).not.toHaveBeenCalled()
+ expect(mockRouter).not.toHaveBeenCalled()
+ })
+
it('does nothing when email is empty', async () => {
mockUserKeys.mockReturnValue(null)
const { result } = renderHook(() => useSignIn())
diff --git a/src/__tests__/useSignUp.test.tsx b/src/__tests__/useSignUp.test.tsx
index a74b5e9..9442886 100644
--- a/src/__tests__/useSignUp.test.tsx
+++ b/src/__tests__/useSignUp.test.tsx
@@ -6,7 +6,9 @@ const mockSetActive = vi.fn()
const mockNavigate = vi.fn()
const mockCreateUserKeys = vi.fn()
const mockSetPassphrase = vi.fn()
-const mockGenerateKeyPair = vi.fn()
+const { mockGenerateKeyPair } = vi.hoisted(() => ({
+ mockGenerateKeyPair: vi.fn(),
+}))
const mockSignUpCreate = vi.fn()
const mockPrepareEmailVerification = vi.fn()
@@ -29,12 +31,6 @@ vi.mock('convex/react', () => ({
useMutation: () => mockCreateUserKeys,
}))
-vi.mock('@/hooks/useEncryption', () => ({
- useEncryption: () => ({
- generateKeyPair: mockGenerateKeyPair,
- }),
-}))
-
vi.mock('jotai', () => ({
useAtom: () => [null, mockSetPassphrase],
}))
@@ -46,6 +42,7 @@ vi.mock('@tanstack/react-router', () => ({
vi.mock('@/lib/crypto', () => ({
createPassphrase: vi.fn().mockReturnValue('mnemonic passphrase'),
encryptPassphrase: vi.fn().mockResolvedValue('encrypted-passphrase'),
+ generateKeyPair: mockGenerateKeyPair,
}))
describe('useSignUp', () => {
@@ -132,6 +129,28 @@ describe('useSignUp', () => {
expect(mockNavigate).toHaveBeenCalled()
})
+ it('sets error and does not setupKeys or navigate when verification fails', async () => {
+ mockAttemptEmailVerification.mockRejectedValue(new Error('Invalid code'))
+
+ const { result } = renderHook(() => useSignUp())
+
+ act(() => {
+ result.current.setEmail('fail@example.com')
+ result.current.setPassword('pass')
+ result.current.setVerificationCode('000000')
+ })
+
+ await act(async () => {
+ result.current.handleVerification({
+ preventDefault: vi.fn(),
+ } as unknown as React.SubmitEvent)
+ })
+
+ expect(result.current.error).toBe('Invalid code')
+ expect(mockCreateUserKeys).not.toHaveBeenCalled()
+ expect(mockNavigate).not.toHaveBeenCalled()
+ })
+
it('sets error on sign-up failure', async () => {
mockSignUpCreate.mockRejectedValue(new Error('Email taken'))
diff --git a/src/hooks/auth.helpers.ts b/src/hooks/auth.helpers.ts
index ca3975a..64e0b27 100644
--- a/src/hooks/auth.helpers.ts
+++ b/src/hooks/auth.helpers.ts
@@ -1,3 +1,4 @@
+import type { RoutesPath } from '@/types/routes'
import { waitForValue } from '@/lib/waitFor'
/**
@@ -12,3 +13,59 @@ export const createSession = async (
await setActive({ session: sessionId })
return waitForValue(getUser)
}
+
+/**
+ * Wraps an async form submit with loading state and error handling.
+ * Resets error, sets loading, runs fn, catches and sets error, always clears loading.
+ */
+export type WithFormSubmitCallbacks = {
+ setError: (err: string | null) => void
+ setIsLoading: (loading: boolean) => void
+ fallbackMessage: string
+}
+
+export const withFormSubmit = async (
+ fn: () => Promise,
+ callbacks: WithFormSubmitCallbacks,
+): Promise => {
+ callbacks.setError(null)
+ callbacks.setIsLoading(true)
+ try {
+ return await fn()
+ } catch (err) {
+ const message =
+ err instanceof Error ? err.message : callbacks.fallbackMessage
+ callbacks.setError(message)
+ return undefined
+ } finally {
+ callbacks.setIsLoading(false)
+ }
+}
+
+/**
+ * Validates session result, activates session, optionally runs beforeRedirect, then navigates.
+ */
+export type CompleteSessionOptions = {
+ setActive: (options: { session: string }) => Promise
+ getUser: () => unknown
+ navigate: (opts: { to: string }) => void
+ route: string
+ beforeRedirect?: () => Promise
+}
+
+export const completeSessionAndRedirect = async (
+ result: { createdSessionId?: string | null },
+ options: CompleteSessionOptions,
+): Promise => {
+ const { setActive, getUser, navigate, route, beforeRedirect } = options
+ if (!result.createdSessionId)
+ throw new Error('No session ID returned from verification')
+
+ await createSession(
+ result.createdSessionId,
+ setActive,
+ getUser as () => unknown,
+ )
+ await beforeRedirect?.()
+ navigate({ to: route })
+}
diff --git a/src/hooks/useEncryption.ts b/src/hooks/useEncryption.ts
index 28fd8fe..79aad09 100644
--- a/src/hooks/useEncryption.ts
+++ b/src/hooks/useEncryption.ts
@@ -1,43 +1,17 @@
-import * as openpgp from 'openpgp'
import { api } from '@convex/_generated/api'
import { useQuery } from 'convex/react'
-import type { PGPKeys } from '@/types/encryption'
+import {
+ decryptWithPrivateKey,
+ encryptWithPublicKey,
+} from '@/lib/crypto'
export const useEncryption = () => {
const userKeys = useQuery(api.userKeys.getUserKeys)
- const generateKeyPair = async (
- email: string,
- passphrase: string,
- ): Promise => {
- const { privateKey, publicKey } = await openpgp.generateKey({
- type: 'ecc',
- curve: 'nistP256',
- userIDs: [{ email }],
- passphrase,
- format: 'armored',
- })
-
- return { privateKey, publicKey }
- }
-
const encryptData = async (data: string): Promise => {
if (!userKeys) throw new Error('No public key available')
- const message = await openpgp.createMessage({
- text: data,
- })
-
- const publicKeyObj = await openpgp.readKey({
- armoredKey: userKeys.publicKey,
- })
-
- const encrypted = await openpgp.encrypt({
- message,
- encryptionKeys: publicKeyObj,
- })
-
- return encrypted as string
+ return encryptWithPublicKey(data, userKeys.publicKey)
}
const decryptData = async (
@@ -47,27 +21,14 @@ export const useEncryption = () => {
if (!userKeys) throw new Error('No public key available')
if (!passphrase) throw new Error('No passphrase available')
- const message = await openpgp.readMessage({
- armoredMessage: encryptedData,
- })
-
- const privateKeyObj = await openpgp.decryptKey({
- privateKey: await openpgp.readPrivateKey({
- armoredKey: userKeys.encryptedPrivateKey,
- }),
+ return decryptWithPrivateKey(
+ encryptedData,
+ userKeys.encryptedPrivateKey,
passphrase,
- })
-
- const { data: decrypted } = await openpgp.decrypt({
- message,
- decryptionKeys: privateKeyObj,
- })
-
- return decrypted as string
+ )
}
return {
- generateKeyPair,
encryptData,
decryptData,
}
diff --git a/src/hooks/useSignIn.ts b/src/hooks/useSignIn.ts
index 609445f..d2d5ef3 100644
--- a/src/hooks/useSignIn.ts
+++ b/src/hooks/useSignIn.ts
@@ -4,8 +4,9 @@ import { useNavigate } from '@tanstack/react-router'
import { api } from '@convex/_generated/api'
import { useQuery } from 'convex/react'
import { useAtom } from 'jotai'
-import { createSession } from './auth.helpers'
-import { EMAIL_CODE_STRATEGY } from '@/types/auth'
+import { completeSessionAndRedirect, withFormSubmit } from './auth.helpers'
+import type { ClerkSignInAttempt } from '@/types/clerk'
+import { AUTH_TIMEOUTS, EMAIL_CODE_STRATEGY } from '@/types/auth'
import { decryptPassphrase } from '@/lib/crypto'
import { passphraseAtom } from '@/stores/encryptionAtoms'
import { RoutesPath } from '@/types/routes'
@@ -18,13 +19,15 @@ export const useSignIn = () => {
const userKeys = useQuery(api.userKeys.getUserKeys)
const userKeysRef = useRef(userKeys)
userKeysRef.current = userKeys
- const [_, setPassphrase] = useAtom(passphraseAtom)
+ const [, setPassphrase] = useAtom(passphraseAtom)
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [code, setCode] = useState('')
- const [isLoading, setIsLoading] = useState(false)
+
const [requiresMfa, setRequiresMfa] = useState(false)
+
+ const [isLoading, setIsLoading] = useState(false)
const [error, setError] = useState(null)
/**
@@ -36,7 +39,8 @@ export const useSignIn = () => {
*/
const authenticateWithPassword = async (): Promise<
- 'complete' | 'needs_mfa'
+ | { outcome: 'complete'; result: ClerkSignInAttempt }
+ | { outcome: 'needs_mfa' }
> => {
const result = await signIn?.create({
strategy: 'password',
@@ -45,24 +49,22 @@ export const useSignIn = () => {
})
if (result?.status === 'complete') {
- if (!setActive) throw new Error('setActive is not available')
- if (!result.createdSessionId)
- throw new Error('No session ID returned from verification')
-
- await createSession(result.createdSessionId, setActive, () => clerk.user)
- return 'complete'
+ return {
+ outcome: 'complete',
+ result: result as unknown as ClerkSignInAttempt,
+ }
}
if (result?.status === 'needs_second_factor') {
- await prepareSecondFactor(result)
+ await prepareSecondFactor(result as unknown as ClerkSignInAttempt)
setRequiresMfa(true)
- return 'needs_mfa'
+ return { outcome: 'needs_mfa' }
}
throw new Error(`Unexpected sign-in result: ${result?.status}`)
}
- const prepareSecondFactor = async (result: any) => {
+ const prepareSecondFactor = async (result: ClerkSignInAttempt) => {
const hasEmailCode = result.supportedSecondFactors?.some(
- (factor: any) => factor.strategy === EMAIL_CODE_STRATEGY,
+ (factor) => factor.strategy === EMAIL_CODE_STRATEGY,
)
if (hasEmailCode) {
@@ -72,21 +74,16 @@ export const useSignIn = () => {
}
}
- const verifyMfaCode = async () => {
+ const verifyMfaCode = async (): Promise => {
const result = await signIn?.attemptSecondFactor({
strategy: EMAIL_CODE_STRATEGY,
code: code.trim(),
})
if (result?.status === 'complete') {
- if (!setActive) throw new Error('setActive is not available')
- if (!result.createdSessionId)
- throw new Error('No session ID returned from verification')
-
- await createSession(result.createdSessionId, setActive, () => clerk.user)
- } else {
- throw new Error(`MFA verification failed: ${result?.status}`)
+ return result as unknown as ClerkSignInAttempt
}
+ throw new Error(`MFA verification failed: ${result?.status}`)
}
const setupPassphrase = async () => {
@@ -95,7 +92,10 @@ export const useSignIn = () => {
userKeysRef.current?.encryptedPassphrase
? userKeysRef.current
: undefined,
- { timeout: 5000, pollInterval: 100 },
+ {
+ timeout: AUTH_TIMEOUTS.USER_KEYS_TIMEOUT,
+ pollInterval: AUTH_TIMEOUTS.USER_KEYS_POLL_INTERVAL,
+ },
)
const decryptedPassphrase = await decryptPassphrase(
@@ -106,41 +106,54 @@ export const useSignIn = () => {
setPassphrase(decryptedPassphrase)
}
+ /**
+ * Handles the sign-in form submission.
+ * @returns A promise that resolves when the sign-in is complete.
+ */
const handleSignIn = async (e: React.SubmitEvent) => {
e.preventDefault()
if (!isLoaded || !email.trim()) return
- setError(null)
- setIsLoading(true)
- try {
- await authenticateWithPassword()
- await setupPassphrase()
- router({ to: RoutesPath.HOME.toString() })
- } catch (err) {
- const message = err instanceof Error ? err.message : 'Sign in failed'
- setError(message)
- } finally {
- setIsLoading(false)
- }
+ await withFormSubmit(
+ async () => {
+ const outcome = await authenticateWithPassword()
+ if (outcome.outcome === 'needs_mfa') return
+
+ await completeSessionAndRedirect(outcome.result, {
+ setActive,
+ getUser: () => clerk.user,
+ navigate: router,
+ route: RoutesPath.HOME,
+ beforeRedirect: setupPassphrase,
+ })
+ },
+ { setError, setIsLoading, fallbackMessage: 'Sign in failed' },
+ )
}
const handleMfaVerify = async (e: React.SubmitEvent) => {
e.preventDefault()
- setError(null)
- setIsLoading(true)
- try {
- await verifyMfaCode()
- await setupPassphrase()
- router({ to: RoutesPath.HOME.toString() })
- } catch (err) {
- const message =
- err instanceof Error ? err.message : 'MFA verification failed'
- setError(message)
- } finally {
- setIsLoading(false)
- }
+ await withFormSubmit(
+ async () => {
+ if (!setActive) throw new Error('setActive is not available')
+ const result = await verifyMfaCode()
+
+ await completeSessionAndRedirect(result, {
+ setActive,
+ getUser: () => clerk.user,
+ navigate: router,
+ route: RoutesPath.HOME,
+ beforeRedirect: setupPassphrase,
+ })
+ },
+ {
+ setError,
+ setIsLoading,
+ fallbackMessage: 'MFA verification failed',
+ },
+ )
}
return {
diff --git a/src/hooks/useSignUp.ts b/src/hooks/useSignUp.ts
index 2f723a4..ad91dbe 100644
--- a/src/hooks/useSignUp.ts
+++ b/src/hooks/useSignUp.ts
@@ -4,26 +4,35 @@ import { useAtom } from 'jotai'
import { useMutation } from 'convex/react'
import { api } from '@convex/_generated/api'
import { useNavigate } from '@tanstack/react-router'
-import { useEncryption } from './useEncryption'
-import { createSession } from './auth.helpers'
-import { EMAIL_CODE_STRATEGY } from '@/types/auth'
+import { completeSessionAndRedirect, withFormSubmit } from './auth.helpers'
+import {
+ createPassphrase,
+ encryptPassphrase,
+ generateKeyPair,
+} from '@/lib/crypto'
import { passphraseAtom } from '@/stores/encryptionAtoms'
+import { EMAIL_CODE_STRATEGY } from '@/types/auth'
import { RoutesPath } from '@/types/routes'
-import { createPassphrase, encryptPassphrase } from '@/lib/crypto'
+
+type SignUpCompleteResult = {
+ status: 'complete'
+ createdSessionId: string
+}
export const useSignUp = () => {
const navigate = useNavigate()
const clerk = useClerk()
const { isLoaded, signUp, setActive } = useClerkSignUp()
const createUserKeys = useMutation(api.userKeys.createUserKeys)
- const [_, setPassphrase] = useAtom(passphraseAtom)
- const { generateKeyPair } = useEncryption()
+ const [, setPassphrase] = useAtom(passphraseAtom)
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [confirmPassword, setConfirmPassword] = useState('')
const [verificationCode, setVerificationCode] = useState('')
+
const [pendingVerification, setPendingVerification] = useState(false)
+
const [isLoading, setIsLoading] = useState(false)
const [error, setError] = useState(null)
@@ -52,20 +61,15 @@ export const useSignUp = () => {
}
}
- const verifyEmailCode = async () => {
+ const verifyEmailCode = async (): Promise => {
const result = await signUp?.attemptEmailAddressVerification({
code: verificationCode,
})
- if (result?.status === 'complete') {
- if (!setActive) throw new Error('setActive is not available')
- if (!result.createdSessionId)
- throw new Error('No session ID returned from verification')
-
- await createSession(result.createdSessionId, setActive, () => clerk.user)
- } else {
- throw new Error(`Verification failed: ${result?.status}`)
+ if (result?.status === 'complete' && result.createdSessionId) {
+ return result as SignUpCompleteResult
}
+ throw new Error(`Verification failed: ${result?.status}`)
}
const setupKeys = async () => {
@@ -82,38 +86,44 @@ export const useSignUp = () => {
setPassphrase(passphrase)
}
+ /**
+ * Handles the sign-up form submission.
+ * @returns A promise that resolves when the sign-up is complete.
+ */
const handleSignUp = async (e: React.SubmitEvent) => {
e.preventDefault()
if (!isLoaded) return
- setError(null)
- setIsLoading(true)
- try {
- await createAccount()
- } catch (err) {
- const message = err instanceof Error ? err.message : 'Sign up failed'
- setError(message)
- } finally {
- setIsLoading(false)
- }
+ await withFormSubmit(() => createAccount(), {
+ setError,
+ setIsLoading,
+ fallbackMessage: 'Sign up failed',
+ })
}
const handleVerification = async (e: React.SubmitEvent) => {
e.preventDefault()
- setError(null)
- setIsLoading(true)
- try {
- await verifyEmailCode()
- } catch (err) {
- const message = err instanceof Error ? err.message : 'Verification failed'
- setError(message)
- } finally {
- await setupKeys()
- setIsLoading(false)
- navigate({ to: RoutesPath.HOME.toString() })
- }
+ await withFormSubmit(
+ async () => {
+ if (!setActive) throw new Error('setActive is not available')
+ const result = await verifyEmailCode()
+
+ await completeSessionAndRedirect(result, {
+ setActive,
+ getUser: () => clerk.user,
+ navigate,
+ route: RoutesPath.HOME,
+ beforeRedirect: setupKeys,
+ })
+ },
+ {
+ setError,
+ setIsLoading,
+ fallbackMessage: 'Verification failed',
+ },
+ )
}
return {
diff --git a/src/lib/crypto.ts b/src/lib/crypto.ts
index a4ef5dd..fad4653 100644
--- a/src/lib/crypto.ts
+++ b/src/lib/crypto.ts
@@ -53,3 +53,81 @@ export const decryptPassphrase = async (
return decrypted as string
}
+
+/**
+ * Encrypt data with a public key
+ * @param data Plaintext to encrypt
+ * @param publicKey Armored public key
+ * @returns Armored encrypted message
+ */
+export const encryptWithPublicKey = async (
+ data: string,
+ publicKey: string,
+): Promise => {
+ const message = await openpgp.createMessage({
+ text: data,
+ })
+
+ const publicKeyObj = await openpgp.readKey({
+ armoredKey: publicKey,
+ })
+
+ const encrypted = await openpgp.encrypt({
+ message,
+ encryptionKeys: publicKeyObj,
+ })
+
+ return encrypted as string
+}
+
+/**
+ * Decrypt data with a private key
+ * @param encryptedData Armored encrypted message
+ * @param encryptedPrivateKey Armored encrypted private key
+ * @param passphrase Passphrase to unlock the private key
+ * @returns Decrypted plaintext
+ */
+export const decryptWithPrivateKey = async (
+ encryptedData: string,
+ encryptedPrivateKey: string,
+ passphrase: string,
+): Promise => {
+ const message = await openpgp.readMessage({
+ armoredMessage: encryptedData,
+ })
+
+ const privateKeyObj = await openpgp.decryptKey({
+ privateKey: await openpgp.readPrivateKey({
+ armoredKey: encryptedPrivateKey,
+ }),
+ passphrase,
+ })
+
+ const { data: decrypted } = await openpgp.decrypt({
+ message,
+ decryptionKeys: privateKeyObj,
+ })
+
+ return decrypted as string
+}
+
+/**
+ * Generate a PGP key pair for a user
+ * @param email User email for key identity
+ * @param passphrase Passphrase to protect the private key
+ * @returns Armored private and public keys
+ */
+export const generateKeyPair = async (
+ email: string,
+ passphrase: string,
+): Promise<{ privateKey: string; publicKey: string }> => {
+ const { privateKey, publicKey } = await openpgp.generateKey({
+ type: 'ecc',
+ curve: 'nistP256',
+ userIDs: [{ email }],
+ passphrase,
+ format: 'armored',
+ })
+
+ return { privateKey, publicKey }
+}
diff --git a/src/types/auth.ts b/src/types/auth.ts
index 28515ea..d0f1f9b 100644
--- a/src/types/auth.ts
+++ b/src/types/auth.ts
@@ -2,23 +2,16 @@
* Authentication and timing constants
* Centralized configuration for timeouts, intervals, and time windows
*/
-
-/**
- * Timeout values for authentication operations (in milliseconds)
- */
export const AUTH_TIMEOUTS = {
- /** Timeout for waiting for Clerk user to be loaded */
- USER_LOAD: 6000,
+ /** Timeout for waiting for user keys after sign-in */
+ USER_KEYS_TIMEOUT: 5000,
- /** Timeout for waiting for Convex auth to propagate */
- CONVEX_AUTH: 5000,
+ /** Polling interval for waiting for user keys */
+ USER_KEYS_POLL_INTERVAL: 100,
/** Polling interval for waiting operations */
POLL_INTERVAL: 200,
-
- /** Time window to consider a sign-in as "recent" (30 seconds) */
- RECENT_SIGNIN_WINDOW: 30000,
-} as const
+}
/**
* Two-factor authentication strategy priority order
@@ -29,7 +22,7 @@ export const TWO_FACTOR_PRIORITY = [
'email_code',
'totp',
'backup_code',
-] as const
+]
export type TwoFactorStrategy = (typeof TWO_FACTOR_PRIORITY)[number]
From 90b6e7de826cfdfefab41fa8e8e76c6c5f561de0 Mon Sep 17 00:00:00 2001
From: jslyonnais <6282845+jslyonnais@users.noreply.github.com>
Date: Fri, 20 Feb 2026 11:14:11 -0500
Subject: [PATCH 15/19] lint
---
src/hooks/useEncryption.ts | 5 +----
1 file changed, 1 insertion(+), 4 deletions(-)
diff --git a/src/hooks/useEncryption.ts b/src/hooks/useEncryption.ts
index 79aad09..2994824 100644
--- a/src/hooks/useEncryption.ts
+++ b/src/hooks/useEncryption.ts
@@ -1,9 +1,6 @@
import { api } from '@convex/_generated/api'
import { useQuery } from 'convex/react'
-import {
- decryptWithPrivateKey,
- encryptWithPublicKey,
-} from '@/lib/crypto'
+import { decryptWithPrivateKey, encryptWithPublicKey } from '@/lib/crypto'
export const useEncryption = () => {
const userKeys = useQuery(api.userKeys.getUserKeys)
From 107ce532e8b688d517b17d9ebb8277a723713bac Mon Sep 17 00:00:00 2001
From: jslyonnais <6282845+jslyonnais@users.noreply.github.com>
Date: Fri, 20 Feb 2026 11:14:28 -0500
Subject: [PATCH 16/19] missing unused
---
src/hooks/auth.helpers.ts | 1 -
1 file changed, 1 deletion(-)
diff --git a/src/hooks/auth.helpers.ts b/src/hooks/auth.helpers.ts
index 64e0b27..87ed1d1 100644
--- a/src/hooks/auth.helpers.ts
+++ b/src/hooks/auth.helpers.ts
@@ -1,4 +1,3 @@
-import type { RoutesPath } from '@/types/routes'
import { waitForValue } from '@/lib/waitFor'
/**
From 4b68b6144755862d6a5a560b727082fdb2d1d317 Mon Sep 17 00:00:00 2001
From: jslyonnais <6282845+jslyonnais@users.noreply.github.com>
Date: Fri, 20 Feb 2026 11:18:31 -0500
Subject: [PATCH 17/19] use router component inside signin
---
src/routes/auth/signin.tsx | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/src/routes/auth/signin.tsx b/src/routes/auth/signin.tsx
index 1c83d39..289bf0a 100644
--- a/src/routes/auth/signin.tsx
+++ b/src/routes/auth/signin.tsx
@@ -1,4 +1,4 @@
-import { createFileRoute, useNavigate } from '@tanstack/react-router'
+import { Navigate, createFileRoute } from '@tanstack/react-router'
import { useUser } from '@clerk/clerk-react'
import { useAtomValue } from 'jotai'
import { SignInView } from '@/views/SignInView'
@@ -10,12 +10,11 @@ export const Route = createFileRoute(RoutesPath.SIGN_IN)({
})
function RouteComponent() {
- const navigate = useNavigate()
const { isSignedIn } = useUser()
const localPassphrase = useAtomValue(passphraseAtom)
if (isSignedIn && localPassphrase)
- return navigate({ to: RoutesPath.HOME.toString() })
+
return (
From 3496824b0fdb9bea941ab75f72f7fc83a3e4769a Mon Sep 17 00:00:00 2001
From: jslyonnais <6282845+jslyonnais@users.noreply.github.com>
Date: Fri, 20 Feb 2026 11:46:55 -0500
Subject: [PATCH 18/19] update packages
---
convex/_generated/api.d.ts | 18 +-
convex/_generated/api.js | 8 +-
convex/_generated/dataModel.d.ts | 14 +-
convex/_generated/server.d.ts | 28 +-
convex/_generated/server.js | 16 +-
package-lock.json | 1050 +++++++++++++-----------------
package.json | 74 ++-
src/__tests__/useSignIn.test.tsx | 12 +-
src/__tests__/useSignUp.test.tsx | 8 +-
9 files changed, 542 insertions(+), 686 deletions(-)
diff --git a/convex/_generated/api.d.ts b/convex/_generated/api.d.ts
index 24f85d2..bcf5f1f 100644
--- a/convex/_generated/api.d.ts
+++ b/convex/_generated/api.d.ts
@@ -8,17 +8,17 @@
* @module
*/
-import type * as userKeys from "../userKeys.js";
+import type * as userKeys from '../userKeys.js'
import type {
ApiFromModules,
FilterApi,
FunctionReference,
-} from "convex/server";
+} from 'convex/server'
declare const fullApi: ApiFromModules<{
- userKeys: typeof userKeys;
-}>;
+ userKeys: typeof userKeys
+}>
/**
* A utility for referencing Convex functions in your app's public API.
@@ -30,8 +30,8 @@ declare const fullApi: ApiFromModules<{
*/
export declare const api: FilterApi<
typeof fullApi,
- FunctionReference
->;
+ FunctionReference
+>
/**
* A utility for referencing Convex functions in your app's internal API.
@@ -43,7 +43,7 @@ export declare const api: FilterApi<
*/
export declare const internal: FilterApi<
typeof fullApi,
- FunctionReference
->;
+ FunctionReference
+>
-export declare const components: {};
+export declare const components: {}
diff --git a/convex/_generated/api.js b/convex/_generated/api.js
index 44bf985..738fe4e 100644
--- a/convex/_generated/api.js
+++ b/convex/_generated/api.js
@@ -8,7 +8,7 @@
* @module
*/
-import { anyApi, componentsGeneric } from "convex/server";
+import { anyApi, componentsGeneric } from 'convex/server'
/**
* A utility for referencing Convex functions in your app's API.
@@ -18,6 +18,6 @@ import { anyApi, componentsGeneric } from "convex/server";
* const myFunctionReference = api.myModule.myFunction;
* ```
*/
-export const api = anyApi;
-export const internal = anyApi;
-export const components = componentsGeneric();
+export const api = anyApi
+export const internal = anyApi
+export const components = componentsGeneric()
diff --git a/convex/_generated/dataModel.d.ts b/convex/_generated/dataModel.d.ts
index f97fd19..d5b5a3b 100644
--- a/convex/_generated/dataModel.d.ts
+++ b/convex/_generated/dataModel.d.ts
@@ -13,14 +13,14 @@ import type {
DocumentByName,
TableNamesInDataModel,
SystemTableNames,
-} from "convex/server";
-import type { GenericId } from "convex/values";
-import schema from "../schema.js";
+} from 'convex/server'
+import type { GenericId } from 'convex/values'
+import schema from '../schema.js'
/**
* The names of all of your Convex tables.
*/
-export type TableNames = TableNamesInDataModel;
+export type TableNames = TableNamesInDataModel
/**
* The type of a document stored in Convex.
@@ -30,7 +30,7 @@ export type TableNames = TableNamesInDataModel;
export type Doc = DocumentByName<
DataModel,
TableName
->;
+>
/**
* An identifier for a document in Convex.
@@ -46,7 +46,7 @@ export type Doc = DocumentByName<
* @typeParam TableName - A string literal type of the table name (like "users").
*/
export type Id =
- GenericId;
+ GenericId
/**
* A type describing your Convex data model.
@@ -57,4 +57,4 @@ export type Id =
* This type is used to parameterize methods like `queryGeneric` and
* `mutationGeneric` to make them type-safe.
*/
-export type DataModel = DataModelFromSchemaDefinition;
+export type DataModel = DataModelFromSchemaDefinition
diff --git a/convex/_generated/server.d.ts b/convex/_generated/server.d.ts
index bec05e6..ccd5e20 100644
--- a/convex/_generated/server.d.ts
+++ b/convex/_generated/server.d.ts
@@ -18,8 +18,8 @@ import {
GenericQueryCtx,
GenericDatabaseReader,
GenericDatabaseWriter,
-} from "convex/server";
-import type { DataModel } from "./dataModel.js";
+} from 'convex/server'
+import type { DataModel } from './dataModel.js'
/**
* Define a query in this Convex app's public API.
@@ -29,7 +29,7 @@ import type { DataModel } from "./dataModel.js";
* @param func - The query function. It receives a {@link QueryCtx} as its first argument.
* @returns The wrapped query. Include this as an `export` to name it and make it accessible.
*/
-export declare const query: QueryBuilder;
+export declare const query: QueryBuilder
/**
* Define a query that is only accessible from other Convex functions (but not from the client).
@@ -39,7 +39,7 @@ export declare const query: QueryBuilder;
* @param func - The query function. It receives a {@link QueryCtx} as its first argument.
* @returns The wrapped query. Include this as an `export` to name it and make it accessible.
*/
-export declare const internalQuery: QueryBuilder;
+export declare const internalQuery: QueryBuilder
/**
* Define a mutation in this Convex app's public API.
@@ -49,7 +49,7 @@ export declare const internalQuery: QueryBuilder;
* @param func - The mutation function. It receives a {@link MutationCtx} as its first argument.
* @returns The wrapped mutation. Include this as an `export` to name it and make it accessible.
*/
-export declare const mutation: MutationBuilder;
+export declare const mutation: MutationBuilder
/**
* Define a mutation that is only accessible from other Convex functions (but not from the client).
@@ -59,7 +59,7 @@ export declare const mutation: MutationBuilder;
* @param func - The mutation function. It receives a {@link MutationCtx} as its first argument.
* @returns The wrapped mutation. Include this as an `export` to name it and make it accessible.
*/
-export declare const internalMutation: MutationBuilder;
+export declare const internalMutation: MutationBuilder
/**
* Define an action in this Convex app's public API.
@@ -72,7 +72,7 @@ export declare const internalMutation: MutationBuilder;
* @param func - The action. It receives an {@link ActionCtx} as its first argument.
* @returns The wrapped action. Include this as an `export` to name it and make it accessible.
*/
-export declare const action: ActionBuilder;
+export declare const action: ActionBuilder
/**
* Define an action that is only accessible from other Convex functions (but not from the client).
@@ -80,7 +80,7 @@ export declare const action: ActionBuilder;
* @param func - The function. It receives an {@link ActionCtx} as its first argument.
* @returns The wrapped function. Include this as an `export` to name it and make it accessible.
*/
-export declare const internalAction: ActionBuilder;
+export declare const internalAction: ActionBuilder
/**
* Define an HTTP action.
@@ -93,7 +93,7 @@ export declare const internalAction: ActionBuilder;
* and a Fetch API `Request` object as its second.
* @returns The wrapped function. Import this function from `convex/http.js` and route it to hook it up.
*/
-export declare const httpAction: HttpActionBuilder;
+export declare const httpAction: HttpActionBuilder
/**
* A set of services for use within Convex query functions.
@@ -104,7 +104,7 @@ export declare const httpAction: HttpActionBuilder;
* This differs from the {@link MutationCtx} because all of the services are
* read-only.
*/
-export type QueryCtx = GenericQueryCtx;
+export type QueryCtx = GenericQueryCtx
/**
* A set of services for use within Convex mutation functions.
@@ -112,7 +112,7 @@ export type QueryCtx = GenericQueryCtx;
* The mutation context is passed as the first argument to any Convex mutation
* function run on the server.
*/
-export type MutationCtx = GenericMutationCtx;
+export type MutationCtx = GenericMutationCtx
/**
* A set of services for use within Convex action functions.
@@ -120,7 +120,7 @@ export type MutationCtx = GenericMutationCtx;
* The action context is passed as the first argument to any Convex action
* function run on the server.
*/
-export type ActionCtx = GenericActionCtx;
+export type ActionCtx = GenericActionCtx
/**
* An interface to read from the database within Convex query functions.
@@ -129,7 +129,7 @@ export type ActionCtx = GenericActionCtx;
* document by its {@link Id}, or {@link DatabaseReader.query}, which starts
* building a query.
*/
-export type DatabaseReader = GenericDatabaseReader;
+export type DatabaseReader = GenericDatabaseReader
/**
* An interface to read from and write to the database within Convex mutation
@@ -140,4 +140,4 @@ export type DatabaseReader = GenericDatabaseReader;
* your data in an inconsistent state. See [the Convex Guide](https://docs.convex.dev/understanding/convex-fundamentals/functions#atomicity-and-optimistic-concurrency-control)
* for the guarantees Convex provides your functions.
*/
-export type DatabaseWriter = GenericDatabaseWriter;
+export type DatabaseWriter = GenericDatabaseWriter
diff --git a/convex/_generated/server.js b/convex/_generated/server.js
index bf3d25a..682fcd2 100644
--- a/convex/_generated/server.js
+++ b/convex/_generated/server.js
@@ -16,7 +16,7 @@ import {
internalActionGeneric,
internalMutationGeneric,
internalQueryGeneric,
-} from "convex/server";
+} from 'convex/server'
/**
* Define a query in this Convex app's public API.
@@ -26,7 +26,7 @@ import {
* @param func - The query function. It receives a {@link QueryCtx} as its first argument.
* @returns The wrapped query. Include this as an `export` to name it and make it accessible.
*/
-export const query = queryGeneric;
+export const query = queryGeneric
/**
* Define a query that is only accessible from other Convex functions (but not from the client).
@@ -36,7 +36,7 @@ export const query = queryGeneric;
* @param func - The query function. It receives a {@link QueryCtx} as its first argument.
* @returns The wrapped query. Include this as an `export` to name it and make it accessible.
*/
-export const internalQuery = internalQueryGeneric;
+export const internalQuery = internalQueryGeneric
/**
* Define a mutation in this Convex app's public API.
@@ -46,7 +46,7 @@ export const internalQuery = internalQueryGeneric;
* @param func - The mutation function. It receives a {@link MutationCtx} as its first argument.
* @returns The wrapped mutation. Include this as an `export` to name it and make it accessible.
*/
-export const mutation = mutationGeneric;
+export const mutation = mutationGeneric
/**
* Define a mutation that is only accessible from other Convex functions (but not from the client).
@@ -56,7 +56,7 @@ export const mutation = mutationGeneric;
* @param func - The mutation function. It receives a {@link MutationCtx} as its first argument.
* @returns The wrapped mutation. Include this as an `export` to name it and make it accessible.
*/
-export const internalMutation = internalMutationGeneric;
+export const internalMutation = internalMutationGeneric
/**
* Define an action in this Convex app's public API.
@@ -69,7 +69,7 @@ export const internalMutation = internalMutationGeneric;
* @param func - The action. It receives an {@link ActionCtx} as its first argument.
* @returns The wrapped action. Include this as an `export` to name it and make it accessible.
*/
-export const action = actionGeneric;
+export const action = actionGeneric
/**
* Define an action that is only accessible from other Convex functions (but not from the client).
@@ -77,7 +77,7 @@ export const action = actionGeneric;
* @param func - The function. It receives an {@link ActionCtx} as its first argument.
* @returns The wrapped function. Include this as an `export` to name it and make it accessible.
*/
-export const internalAction = internalActionGeneric;
+export const internalAction = internalActionGeneric
/**
* Define an HTTP action.
@@ -90,4 +90,4 @@ export const internalAction = internalActionGeneric;
* and a Fetch API `Request` object as its second.
* @returns The wrapped function. Import this function from `convex/http.js` and route it to hook it up.
*/
-export const httpAction = httpActionGeneric;
+export const httpAction = httpActionGeneric
diff --git a/package-lock.json b/package-lock.json
index 323bac7..369b015 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -6,56 +6,56 @@
"": {
"name": "e2ee-base",
"dependencies": {
- "@clerk/clerk-react": "^5.49.0",
+ "@clerk/clerk-react": "^5.61.0",
"@convex-dev/react-query": "0.1.0",
"@scure/bip39": "^2.0.1",
- "@t3-oss/env-core": "^0.13.8",
- "@tailwindcss/vite": "^4.1.18",
- "@tanstack/react-devtools": "^0.7.0",
- "@tanstack/react-query": "^5.66.5",
- "@tanstack/react-query-devtools": "^5.84.2",
- "@tanstack/react-router": "^1.132.0",
- "@tanstack/react-router-devtools": "^1.132.0",
- "@tanstack/react-router-ssr-query": "^1.131.7",
- "@tanstack/react-start": "^1.132.0",
- "@tanstack/react-store": "^0.8.0",
- "@tanstack/router-plugin": "^1.132.0",
- "@tanstack/store": "^0.8.0",
+ "@t3-oss/env-core": "^0.13.10",
+ "@tailwindcss/vite": "^4.2.0",
+ "@tanstack/react-devtools": "^0.9.6",
+ "@tanstack/react-query": "^5.90.21",
+ "@tanstack/react-query-devtools": "^5.91.3",
+ "@tanstack/react-router": "^1.161.3",
+ "@tanstack/react-router-devtools": "^1.161.3",
+ "@tanstack/react-router-ssr-query": "^1.161.3",
+ "@tanstack/react-start": "^1.161.3",
+ "@tanstack/react-store": "^0.9.1",
+ "@tanstack/router-plugin": "^1.161.3",
+ "@tanstack/store": "^0.9.1",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"convex": "^1.32.0",
"jotai": "^2.18.0",
"jotai-devtools": "^0.13.0",
- "lucide-react": "^0.561.0",
+ "lucide-react": "^0.575.0",
"nitro": "npm:nitro-nightly@latest",
"openpgp": "^6.3.0",
"radix-ui": "^1.4.3",
- "react": "^19.2.0",
- "react-dom": "^19.2.0",
- "tailwind-merge": "^3.0.2",
- "tailwindcss": "^4.1.18",
- "tw-animate-css": "^1.3.6",
- "vite-tsconfig-paths": "^5.1.4",
- "zod": "^4.1.11"
+ "react": "^19.2.4",
+ "react-dom": "^19.2.4",
+ "tailwind-merge": "^3.5.0",
+ "tailwindcss": "^4.2.0",
+ "tw-animate-css": "^1.4.0",
+ "vite-tsconfig-paths": "^6.1.1",
+ "zod": "^4.3.6"
},
"devDependencies": {
- "@inlang/paraglide-js": "^2.8.0",
+ "@inlang/paraglide-js": "^2.12.0",
"@tanstack/devtools-event-client": "^0.4.0",
- "@tanstack/devtools-vite": "^0.3.11",
- "@tanstack/eslint-config": "^0.3.0",
- "@testing-library/dom": "^10.4.0",
- "@testing-library/react": "^16.2.0",
- "@types/node": "^22.10.2",
- "@types/react": "^19.2.0",
- "@types/react-dom": "^19.2.0",
- "@vitejs/plugin-react": "^5.0.4",
+ "@tanstack/devtools-vite": "^0.5.1",
+ "@tanstack/eslint-config": "^0.4.0",
+ "@testing-library/dom": "^10.4.1",
+ "@testing-library/react": "^16.3.2",
+ "@types/node": "^25.3.0",
+ "@types/react": "^19.2.14",
+ "@types/react-dom": "^19.2.3",
+ "@vitejs/plugin-react": "^5.1.4",
"husky": "^9.1.7",
"jotai-babel": "^0.1.0",
- "jsdom": "^27.0.0",
- "prettier": "^3.5.3",
- "typescript": "^5.7.2",
- "vite": "^7.1.7",
- "vitest": "^3.0.5"
+ "jsdom": "^28.1.0",
+ "prettier": "^3.8.1",
+ "typescript": "^5.9.3",
+ "vite": "^7.3.1",
+ "vitest": "^4.0.18"
}
},
"node_modules/@acemir/cssom": {
@@ -424,13 +424,26 @@
"node": ">=6.9.0"
}
},
+ "node_modules/@bramus/specificity": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz",
+ "integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "css-tree": "^3.0.0"
+ },
+ "bin": {
+ "specificity": "bin/cli.js"
+ }
+ },
"node_modules/@clerk/clerk-react": {
- "version": "5.60.2",
- "resolved": "https://registry.npmjs.org/@clerk/clerk-react/-/clerk-react-5.60.2.tgz",
- "integrity": "sha512-eQpY85y3hqcrSyBKdfYXTmRRvy2B+FAwiX9hQmCECWy5G00vDgPnc3NL37FsSGiWPY5VvCCXbEt8usbgfvcXQQ==",
+ "version": "5.61.0",
+ "resolved": "https://registry.npmjs.org/@clerk/clerk-react/-/clerk-react-5.61.0.tgz",
+ "integrity": "sha512-M20kv1rSftll7pZdc3gD5jppcbJSHWDnFHr26CXDOzsqLCYP4ESlqJgv75U0PUcNoM4TgmqsbAP5MrbWIx5Xmg==",
"license": "MIT",
"dependencies": {
- "@clerk/shared": "^3.45.1",
+ "@clerk/shared": "^3.46.0",
"tslib": "2.8.1"
},
"engines": {
@@ -442,9 +455,9 @@
}
},
"node_modules/@clerk/shared": {
- "version": "3.45.1",
- "resolved": "https://registry.npmjs.org/@clerk/shared/-/shared-3.45.1.tgz",
- "integrity": "sha512-TqbnQnufBIz/W3yfbUQI+fK5CgIKYysXEQHL4CKIuvn2m7hRFjT2qqfHNYc8mzb3eI7+p3NyUezOGVn7FTf1pA==",
+ "version": "3.46.0",
+ "resolved": "https://registry.npmjs.org/@clerk/shared/-/shared-3.46.0.tgz",
+ "integrity": "sha512-JPjpGehaqaWVf9o3EDYpxjQJd0T+ZuzWATbhE+V/Z9hOKvBxbtuKfAAIr/F9o8EbFB5SM+//Vmhs+s6uoAQa5w==",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
@@ -613,6 +626,12 @@
"node": ">=20.19.0"
}
},
+ "node_modules/@dmsnell/diff-match-patch": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@dmsnell/diff-match-patch/-/diff-match-patch-1.1.0.tgz",
+ "integrity": "sha512-yejLPmM5pjsGvxS9gXablUSbInW7H976c/FJ4iQxWIm7/38xBySRemTPDe34lhg1gVLbJntX0+sH0jYfU+PN9A==",
+ "license": "Apache-2.0"
+ },
"node_modules/@emnapi/core": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.8.1.tgz",
@@ -1171,6 +1190,24 @@
"url": "https://opencollective.com/eslint"
}
},
+ "node_modules/@eslint/eslintrc/node_modules/ajv": {
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
"node_modules/@eslint/eslintrc/node_modules/globals": {
"version": "14.0.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
@@ -1185,12 +1222,21 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
"node_modules/@eslint/js": {
- "version": "9.39.2",
- "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz",
- "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==",
+ "version": "9.39.3",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.3.tgz",
+ "integrity": "sha512-1B1VkCq6FuUNlQvlBYb+1jDu/gV297TIs/OeiaSR9l1H27SVW55ONE1e1Vp16NqP683+xEGzxYtv4XCiDPaQiw==",
"dev": true,
"license": "MIT",
+ "peer": true,
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
@@ -3774,6 +3820,13 @@
"sqlite-wasm": "bin/index.js"
}
},
+ "node_modules/@standard-schema/spec": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
+ "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@stylistic/eslint-plugin": {
"version": "5.8.0",
"resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-5.8.0.tgz",
@@ -4079,16 +4132,16 @@
}
},
"node_modules/@tanstack/devtools": {
- "version": "0.7.0",
- "resolved": "https://registry.npmjs.org/@tanstack/devtools/-/devtools-0.7.0.tgz",
- "integrity": "sha512-AlAoCqJhWLg9GBEaoV1g/j+X/WA1aJSWOsekxeuZpYeS2hdVuKAjj04KQLUMJhtLfNl2s2E+TCj7ZRtWyY3U4w==",
+ "version": "0.10.7",
+ "resolved": "https://registry.npmjs.org/@tanstack/devtools/-/devtools-0.10.7.tgz",
+ "integrity": "sha512-ScwnFjJTMRUd6miQax7sEhq9winalQIVhm0MTX3YfjoGjMzB/jzjzYlLOraen8hcxMHH9CifTjio8ZVdqSRBRg==",
"license": "MIT",
"dependencies": {
"@solid-primitives/event-listener": "^2.4.3",
"@solid-primitives/keyboard": "^1.3.3",
"@solid-primitives/resize-observer": "^2.1.3",
- "@tanstack/devtools-client": "0.0.3",
- "@tanstack/devtools-event-bus": "0.3.3",
+ "@tanstack/devtools-client": "0.0.5",
+ "@tanstack/devtools-event-bus": "0.4.1",
"@tanstack/devtools-ui": "0.4.4",
"clsx": "^2.1.1",
"goober": "^2.1.16",
@@ -4109,7 +4162,6 @@
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/@tanstack/devtools-client/-/devtools-client-0.0.5.tgz",
"integrity": "sha512-hsNDE3iu4frt9cC2ppn1mNRnLKo2uc1/1hXAyY9z4UYb+o40M2clFAhiFoo4HngjfGJDV3x18KVVIq7W4Un+zA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@tanstack/devtools-event-client": "^0.4.0"
@@ -4123,9 +4175,9 @@
}
},
"node_modules/@tanstack/devtools-event-bus": {
- "version": "0.3.3",
- "resolved": "https://registry.npmjs.org/@tanstack/devtools-event-bus/-/devtools-event-bus-0.3.3.tgz",
- "integrity": "sha512-lWl88uLAz7ZhwNdLH6A3tBOSEuBCrvnY9Fzr5JPdzJRFdM5ZFdyNWz1Bf5l/F3GU57VodrN0KCFi9OA26H5Kpg==",
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/@tanstack/devtools-event-bus/-/devtools-event-bus-0.4.1.tgz",
+ "integrity": "sha512-cNnJ89Q021Zf883rlbBTfsaxTfi2r73/qejGtyTa7ksErF3hyDyAq1aTbo5crK9dAL7zSHh9viKY1BtMls1QOA==",
"license": "MIT",
"dependencies": {
"ws": "^8.18.3"
@@ -4142,7 +4194,6 @@
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/@tanstack/devtools-event-client/-/devtools-event-client-0.4.0.tgz",
"integrity": "sha512-RPfGuk2bDZgcu9bAJodvO2lnZeHuz4/71HjZ0bGb/SPg8+lyTA+RLSKQvo7fSmPSi8/vcH3aKQ8EM9ywf1olaw==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
@@ -4174,9 +4225,9 @@
}
},
"node_modules/@tanstack/devtools-vite": {
- "version": "0.3.12",
- "resolved": "https://registry.npmjs.org/@tanstack/devtools-vite/-/devtools-vite-0.3.12.tgz",
- "integrity": "sha512-fGJgu4xUhKmGk+a+/aHD8l5HKVk6+ObA+6D3YC3xCXbai/YmaGhztqcZf1tKUqjZyYyQLHsjqmKzvJgVpQP1jw==",
+ "version": "0.5.1",
+ "resolved": "https://registry.npmjs.org/@tanstack/devtools-vite/-/devtools-vite-0.5.1.tgz",
+ "integrity": "sha512-5dXxMznSxx8NNpO9IbDC011sIdvTVvsoLaLAxm69dgDAX0+2OB8gdXrQp8dnzeNMvszKCgRxI2cgr/pjPgmnNw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -4186,7 +4237,7 @@
"@babel/traverse": "^7.28.4",
"@babel/types": "^7.28.4",
"@tanstack/devtools-client": "0.0.5",
- "@tanstack/devtools-event-bus": "0.3.3",
+ "@tanstack/devtools-event-bus": "0.4.1",
"chalk": "^5.6.2",
"launch-editor": "^2.11.1",
"picomatch": "^4.0.3"
@@ -4202,13 +4253,20 @@
"vite": "^6.0.0 || ^7.0.0"
}
},
- "node_modules/@tanstack/devtools/node_modules/@tanstack/devtools-client": {
- "version": "0.0.3",
- "resolved": "https://registry.npmjs.org/@tanstack/devtools-client/-/devtools-client-0.0.3.tgz",
- "integrity": "sha512-kl0r6N5iIL3t9gGDRAv55VRM3UIyMKVH83esRGq7xBjYsRLe/BeCIN2HqrlJkObUXQMKhy7i8ejuGOn+bDqDBw==",
+ "node_modules/@tanstack/eslint-config": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/@tanstack/eslint-config/-/eslint-config-0.4.0.tgz",
+ "integrity": "sha512-V+Cd81W/f65dqKJKpytbwTGx9R+IwxKAHsG/uJ3nSLYEh36hlAr54lRpstUhggQB8nf/cP733cIw8DuD2dzQUg==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "@tanstack/devtools-event-client": "^0.3.3"
+ "@eslint/js": "^10.0.1",
+ "@stylistic/eslint-plugin": "^5.8.0",
+ "eslint-plugin-import-x": "^4.16.1",
+ "eslint-plugin-n": "^17.24.0",
+ "globals": "^17.3.0",
+ "typescript-eslint": "^8.55.0",
+ "vue-eslint-parser": "^10.4.0"
},
"engines": {
"node": ">=18"
@@ -4216,45 +4274,30 @@
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
- }
- },
- "node_modules/@tanstack/devtools/node_modules/@tanstack/devtools-event-client": {
- "version": "0.3.5",
- "resolved": "https://registry.npmjs.org/@tanstack/devtools-event-client/-/devtools-event-client-0.3.5.tgz",
- "integrity": "sha512-RL1f5ZlfZMpghrCIdzl6mLOFLTuhqmPNblZgBaeKfdtk5rfbjykurv+VfYydOFXj0vxVIoA2d/zT7xfD7Ph8fw==",
- "license": "MIT",
- "engines": {
- "node": ">=18"
},
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/tannerlinsley"
+ "peerDependencies": {
+ "eslint": "^9.0.0 || ^10.0.0"
}
},
- "node_modules/@tanstack/eslint-config": {
- "version": "0.3.4",
- "resolved": "https://registry.npmjs.org/@tanstack/eslint-config/-/eslint-config-0.3.4.tgz",
- "integrity": "sha512-5Ou1XWJRCTx5G8WoCbT7+6nQ4iNdsISzBAc4lXpFy2fEOO7xioOSPvcPIv+r9V0drPPETou2tr6oLGZZ909FKg==",
+ "node_modules/@tanstack/eslint-config/node_modules/@eslint/js": {
+ "version": "10.0.1",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz",
+ "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "@eslint/js": "^9.37.0",
- "@stylistic/eslint-plugin": "^5.4.0",
- "eslint-plugin-import-x": "^4.16.1",
- "eslint-plugin-n": "^17.23.1",
- "globals": "^16.5.0",
- "typescript-eslint": "^8.46.0",
- "vue-eslint-parser": "^10.2.0"
- },
"engines": {
- "node": ">=18"
+ "node": "^20.19.0 || ^22.13.0 || >=24"
},
"funding": {
- "type": "github",
- "url": "https://github.com/sponsors/tannerlinsley"
+ "url": "https://eslint.org/donate"
},
"peerDependencies": {
- "eslint": "^8.0.0 || ^9.0.0"
+ "eslint": "^10.0.0"
+ },
+ "peerDependenciesMeta": {
+ "eslint": {
+ "optional": true
+ }
}
},
"node_modules/@tanstack/history": {
@@ -4291,12 +4334,12 @@
}
},
"node_modules/@tanstack/react-devtools": {
- "version": "0.7.11",
- "resolved": "https://registry.npmjs.org/@tanstack/react-devtools/-/react-devtools-0.7.11.tgz",
- "integrity": "sha512-a2Lmz8x+JoDrsU6f7uKRcyyY+k8mA/n5mb9h7XJ3Fz/y3+sPV9t7vAW1s5lyNkQyyDt6V1Oim99faLthoJSxMw==",
+ "version": "0.9.6",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-devtools/-/react-devtools-0.9.6.tgz",
+ "integrity": "sha512-4wnhqQ1o5PnmEDV8L3yLWaE2ZWD2xjdUw1X8Uv5NK9Ekrz/Qr6iuYl+X4Kq9+Ix2luVGMqd3toFvEwkr3uMFBw==",
"license": "MIT",
"dependencies": {
- "@tanstack/devtools": "0.7.0"
+ "@tanstack/devtools": "0.10.7"
},
"engines": {
"node": ">=18"
@@ -4346,14 +4389,14 @@
}
},
"node_modules/@tanstack/react-router": {
- "version": "1.161.1",
- "resolved": "https://registry.npmjs.org/@tanstack/react-router/-/react-router-1.161.1.tgz",
- "integrity": "sha512-RQlCaunj+sleC8/JLxd22sWNpwqTHftcRdwGwNF27tjEzTnj06C6azWmA5sGclTdxGVclEOc/eaW7bUv5klsNw==",
+ "version": "1.161.3",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-router/-/react-router-1.161.3.tgz",
+ "integrity": "sha512-evYPrkuFt4T6E0WVyBGGq83lWHJjsYy3E5SpPpfPY/uRnEgmgwfr6Xl570msRnWYMj7DIkYg8ZWFFwzqKrSlBw==",
"license": "MIT",
"dependencies": {
"@tanstack/history": "1.154.14",
- "@tanstack/react-store": "^0.8.0",
- "@tanstack/router-core": "1.161.1",
+ "@tanstack/react-store": "^0.9.1",
+ "@tanstack/router-core": "1.161.3",
"isbot": "^5.1.22",
"tiny-invariant": "^1.3.3",
"tiny-warning": "^1.0.3"
@@ -4371,12 +4414,12 @@
}
},
"node_modules/@tanstack/react-router-devtools": {
- "version": "1.161.1",
- "resolved": "https://registry.npmjs.org/@tanstack/react-router-devtools/-/react-router-devtools-1.161.1.tgz",
- "integrity": "sha512-fl+o760gCHbd4Nb64SpVJQjpe77xDh2Mx6NqZy0aKACXvWRd8CDcFPzSvDZu4s7tHqFKMfzXqhNzL/jT+A8Prg==",
+ "version": "1.161.3",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-router-devtools/-/react-router-devtools-1.161.3.tgz",
+ "integrity": "sha512-AlJPtaYvhDVuwe/TqZIYt5njmxAGxMEq6l7AXOXQLVu7UP0jysxGoQfrm2LZT+piMeUmJ5opRUTnxktpCphIFQ==",
"license": "MIT",
"dependencies": {
- "@tanstack/router-devtools-core": "1.161.1"
+ "@tanstack/router-devtools-core": "1.161.3"
},
"engines": {
"node": ">=12"
@@ -4386,8 +4429,8 @@
"url": "https://github.com/sponsors/tannerlinsley"
},
"peerDependencies": {
- "@tanstack/react-router": "^1.161.1",
- "@tanstack/router-core": "^1.161.1",
+ "@tanstack/react-router": "^1.161.3",
+ "@tanstack/router-core": "^1.161.3",
"react": ">=18.0.0 || >=19.0.0",
"react-dom": ">=18.0.0 || >=19.0.0"
},
@@ -4398,12 +4441,12 @@
}
},
"node_modules/@tanstack/react-router-ssr-query": {
- "version": "1.161.1",
- "resolved": "https://registry.npmjs.org/@tanstack/react-router-ssr-query/-/react-router-ssr-query-1.161.1.tgz",
- "integrity": "sha512-5pxiUO+0O+06L/gfSpVok4wQnHMMIswalxoXEpcO2oFZWgSzcceY8qNxsm8autOfBNLFTZsJs3hoZ79NSPZ7SA==",
+ "version": "1.161.3",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-router-ssr-query/-/react-router-ssr-query-1.161.3.tgz",
+ "integrity": "sha512-0tLk3yrbQlcHBwbgSPT5I67rzq/v3KXZ4pSsQ7pkk4jgNTldE7sV8haHTiMU8yXI78HajgwMEZrfBvx0mrQfIQ==",
"license": "MIT",
"dependencies": {
- "@tanstack/router-ssr-query-core": "1.161.1"
+ "@tanstack/router-ssr-query-core": "1.161.3"
},
"engines": {
"node": ">=12"
@@ -4421,18 +4464,18 @@
}
},
"node_modules/@tanstack/react-start": {
- "version": "1.161.1",
- "resolved": "https://registry.npmjs.org/@tanstack/react-start/-/react-start-1.161.1.tgz",
- "integrity": "sha512-raK41Qqzdkk6RTN93QA9R2C3MfjoR8VBtRcJjtnFg+FBCOxGZILWIALBuqSGoyGtI0N7ROjczNtyoxR8XV74Ug==",
+ "version": "1.161.3",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-start/-/react-start-1.161.3.tgz",
+ "integrity": "sha512-wWx+wuR4U6yHgnNsHLocjMYrsORdOWom6UuBzOsh+FmFJ1RK+NcjyNNt6WjsmWPU5ZUZ31KHKe4ZEt27R+15IA==",
"license": "MIT",
"dependencies": {
- "@tanstack/react-router": "1.161.1",
- "@tanstack/react-start-client": "1.161.1",
- "@tanstack/react-start-server": "1.161.1",
+ "@tanstack/react-router": "1.161.3",
+ "@tanstack/react-start-client": "1.161.3",
+ "@tanstack/react-start-server": "1.161.3",
"@tanstack/router-utils": "^1.158.0",
- "@tanstack/start-client-core": "1.161.1",
- "@tanstack/start-plugin-core": "1.161.1",
- "@tanstack/start-server-core": "1.161.1",
+ "@tanstack/start-client-core": "1.161.3",
+ "@tanstack/start-plugin-core": "1.161.3",
+ "@tanstack/start-server-core": "1.161.3",
"pathe": "^2.0.3"
},
"engines": {
@@ -4449,14 +4492,14 @@
}
},
"node_modules/@tanstack/react-start-client": {
- "version": "1.161.1",
- "resolved": "https://registry.npmjs.org/@tanstack/react-start-client/-/react-start-client-1.161.1.tgz",
- "integrity": "sha512-UgLdjFMtFna8wcoWXGqGeUHaaYuXJvDfUXQz5HLNSJ+hmcADK6ve0m1LjFY+7yo3qBPiiw7Sld0iUOD/eJrVow==",
+ "version": "1.161.3",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-start-client/-/react-start-client-1.161.3.tgz",
+ "integrity": "sha512-MMyc8WCBkoipJ5rNqgAANOB4gITsfxs/f05pY3K1BUpUAOi91s8YgNO2IBnIOj8sqNoI0Gx2lWgQBCwm4H4+wg==",
"license": "MIT",
"dependencies": {
- "@tanstack/react-router": "1.161.1",
- "@tanstack/router-core": "1.161.1",
- "@tanstack/start-client-core": "1.161.1",
+ "@tanstack/react-router": "1.161.3",
+ "@tanstack/router-core": "1.161.3",
+ "@tanstack/start-client-core": "1.161.3",
"tiny-invariant": "^1.3.3",
"tiny-warning": "^1.0.3"
},
@@ -4473,16 +4516,16 @@
}
},
"node_modules/@tanstack/react-start-server": {
- "version": "1.161.1",
- "resolved": "https://registry.npmjs.org/@tanstack/react-start-server/-/react-start-server-1.161.1.tgz",
- "integrity": "sha512-/Up/iTysuzg63qZOYUdMZfqAQOLg/AA9AJdSR7hzrGdxFsqzT4KyUjYyuzsGet8ips8bbv1GC2dByq7MdG5neg==",
+ "version": "1.161.3",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-start-server/-/react-start-server-1.161.3.tgz",
+ "integrity": "sha512-5FY3+2LHPPlVkHrrPbhi+TKVl93UCWKC0Ta/hxhXBaYrvBU1uWhIuKgspXT+cWP6XcMPuPrc4qfVWrXHKBMIqg==",
"license": "MIT",
"dependencies": {
"@tanstack/history": "1.154.14",
- "@tanstack/react-router": "1.161.1",
- "@tanstack/router-core": "1.161.1",
- "@tanstack/start-client-core": "1.161.1",
- "@tanstack/start-server-core": "1.161.1"
+ "@tanstack/react-router": "1.161.3",
+ "@tanstack/router-core": "1.161.3",
+ "@tanstack/start-client-core": "1.161.3",
+ "@tanstack/start-server-core": "1.161.3"
},
"engines": {
"node": ">=22.12.0"
@@ -4497,12 +4540,12 @@
}
},
"node_modules/@tanstack/react-store": {
- "version": "0.8.1",
- "resolved": "https://registry.npmjs.org/@tanstack/react-store/-/react-store-0.8.1.tgz",
- "integrity": "sha512-XItJt+rG8c5Wn/2L/bnxys85rBpm0BfMbhb4zmPVLXAKY9POrp1xd6IbU4PKoOI+jSEGc3vntPRfLGSgXfE2Ig==",
+ "version": "0.9.1",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-store/-/react-store-0.9.1.tgz",
+ "integrity": "sha512-YzJLnRvy5lIEFTLWBAZmcOjK3+2AepnBv/sr6NZmiqJvq7zTQggyK99Gw8fqYdMdHPQWXjz0epFKJXC+9V2xDA==",
"license": "MIT",
"dependencies": {
- "@tanstack/store": "0.8.1",
+ "@tanstack/store": "0.9.1",
"use-sync-external-store": "^1.6.0"
},
"funding": {
@@ -4515,13 +4558,13 @@
}
},
"node_modules/@tanstack/router-core": {
- "version": "1.161.1",
- "resolved": "https://registry.npmjs.org/@tanstack/router-core/-/router-core-1.161.1.tgz",
- "integrity": "sha512-Ika9RBvxB5cE+ziLxq90rqwhl9sb+j6mlGkRDwuDaGSDODenFeCDzjE0YQlgQ/kBUdSK2K1fFBiQPy5cnl54Og==",
+ "version": "1.161.3",
+ "resolved": "https://registry.npmjs.org/@tanstack/router-core/-/router-core-1.161.3.tgz",
+ "integrity": "sha512-8EuaGXLUjugQE9Rsb8VrWSy+wImcs/DZ9JORqUJYCmiiWnJzbat8KedQItq/9LCjMJyx4vTLCt8NnZCL+j1Ayg==",
"license": "MIT",
"dependencies": {
"@tanstack/history": "1.154.14",
- "@tanstack/store": "^0.8.0",
+ "@tanstack/store": "^0.9.1",
"cookie-es": "^2.0.0",
"seroval": "^1.4.2",
"seroval-plugins": "^1.4.2",
@@ -4537,9 +4580,9 @@
}
},
"node_modules/@tanstack/router-devtools-core": {
- "version": "1.161.1",
- "resolved": "https://registry.npmjs.org/@tanstack/router-devtools-core/-/router-devtools-core-1.161.1.tgz",
- "integrity": "sha512-I3BcTUD2D8l1sKkab4JJM5LHwwWX5sDCbbhD+MGWplycIujzaW7xADbOnwLpeDjtJarc8kY20cUQ2NJ2eaX9kw==",
+ "version": "1.161.3",
+ "resolved": "https://registry.npmjs.org/@tanstack/router-devtools-core/-/router-devtools-core-1.161.3.tgz",
+ "integrity": "sha512-yLbBH9ovomvxAk4nbTzN+UacPX2C5r3Kq4p+4O8gZVopUjRqiYiQN7ZJ6tN6atQouJQtym2xXwa5pC4EyFlCgQ==",
"license": "MIT",
"dependencies": {
"clsx": "^2.1.1",
@@ -4554,7 +4597,7 @@
"url": "https://github.com/sponsors/tannerlinsley"
},
"peerDependencies": {
- "@tanstack/router-core": "^1.161.1",
+ "@tanstack/router-core": "^1.161.3",
"csstype": "^3.0.10"
},
"peerDependenciesMeta": {
@@ -4564,12 +4607,12 @@
}
},
"node_modules/@tanstack/router-generator": {
- "version": "1.161.1",
- "resolved": "https://registry.npmjs.org/@tanstack/router-generator/-/router-generator-1.161.1.tgz",
- "integrity": "sha512-IvkjrSaqr3WzYDUjdXOug1x5MhJT5Pw+hKkAi+GDA4isaBjyXS71QmY3jhsZZ2Rz08Xjw2JkAoIJCxfqw6AQKw==",
+ "version": "1.161.3",
+ "resolved": "https://registry.npmjs.org/@tanstack/router-generator/-/router-generator-1.161.3.tgz",
+ "integrity": "sha512-GKOrsOu7u5aoK1+lRu6KUUOmbb42mYF2ezfXf27QMiBjMx/yDHXln8wmdR7ZQ+FdSGz2YVubt2Ns3KuFsDsZJg==",
"license": "MIT",
"dependencies": {
- "@tanstack/router-core": "1.161.1",
+ "@tanstack/router-core": "1.161.3",
"@tanstack/router-utils": "1.158.0",
"@tanstack/virtual-file-routes": "1.154.7",
"prettier": "^3.5.0",
@@ -4596,9 +4639,9 @@
}
},
"node_modules/@tanstack/router-plugin": {
- "version": "1.161.1",
- "resolved": "https://registry.npmjs.org/@tanstack/router-plugin/-/router-plugin-1.161.1.tgz",
- "integrity": "sha512-1veqinPZRJMWJSgKljk3XF6l9PaDRRqnc2FMEGBRJ5ycmDqvzCP4RaKbA5pfE/DbXHkKF5Z7BiAeateZHgm4jA==",
+ "version": "1.161.3",
+ "resolved": "https://registry.npmjs.org/@tanstack/router-plugin/-/router-plugin-1.161.3.tgz",
+ "integrity": "sha512-3Uy4AxgHNYjmCGf2WYWB8Gy3C6m0YE5DV1SK2p3yUrA/PhCMYRe+xzjyD5pViMUSLUoPHQYGY6bOIM9OOPRI/Q==",
"license": "MIT",
"dependencies": {
"@babel/core": "^7.28.5",
@@ -4607,8 +4650,8 @@
"@babel/template": "^7.27.2",
"@babel/traverse": "^7.28.5",
"@babel/types": "^7.28.5",
- "@tanstack/router-core": "1.161.1",
- "@tanstack/router-generator": "1.161.1",
+ "@tanstack/router-core": "1.161.3",
+ "@tanstack/router-generator": "1.161.3",
"@tanstack/router-utils": "1.158.0",
"@tanstack/virtual-file-routes": "1.154.7",
"chokidar": "^3.6.0",
@@ -4624,7 +4667,7 @@
},
"peerDependencies": {
"@rsbuild/core": ">=1.0.2",
- "@tanstack/react-router": "^1.161.1",
+ "@tanstack/react-router": "^1.161.3",
"vite": ">=5.0.0 || >=6.0.0 || >=7.0.0",
"vite-plugin-solid": "^2.11.10",
"webpack": ">=5.92.0"
@@ -4657,9 +4700,9 @@
}
},
"node_modules/@tanstack/router-ssr-query-core": {
- "version": "1.161.1",
- "resolved": "https://registry.npmjs.org/@tanstack/router-ssr-query-core/-/router-ssr-query-core-1.161.1.tgz",
- "integrity": "sha512-nrmOiBjnG7w1YVLu0Kp+ndJl+NKgQJMoz9NcauckCen0CDF/UuPyEgGQdPVX450tbPwX4DHmUZ3jAGlm9F/pLQ==",
+ "version": "1.161.3",
+ "resolved": "https://registry.npmjs.org/@tanstack/router-ssr-query-core/-/router-ssr-query-core-1.161.3.tgz",
+ "integrity": "sha512-dFEWRKnv4UVWOGY53ypFivHkuM/NKxtGuj7O0UGHaQshpVEH+oXfb/KupHeCuSbDLdREaqQYMBq0L6VfHRw5FA==",
"license": "MIT",
"engines": {
"node": ">=12"
@@ -4698,14 +4741,14 @@
}
},
"node_modules/@tanstack/start-client-core": {
- "version": "1.161.1",
- "resolved": "https://registry.npmjs.org/@tanstack/start-client-core/-/start-client-core-1.161.1.tgz",
- "integrity": "sha512-zivAFxPCXgQ4S1eRqWJGCiRE4vMof+vYA5afho1ut20F8NHCByQXCcVoDI0wGvBH79cYiW/LPBtx1uDqLmaRqQ==",
+ "version": "1.161.3",
+ "resolved": "https://registry.npmjs.org/@tanstack/start-client-core/-/start-client-core-1.161.3.tgz",
+ "integrity": "sha512-tE9PJCk+64CeIie70f6MZd8LP3A+5LWjjwksEaqxsZMYGN0Re6BWI/oTpZtnvRHhtCUB5ASz6K/eMZt8R9wq5A==",
"license": "MIT",
"dependencies": {
- "@tanstack/router-core": "1.161.1",
+ "@tanstack/router-core": "1.161.3",
"@tanstack/start-fn-stubs": "1.154.7",
- "@tanstack/start-storage-context": "1.161.1",
+ "@tanstack/start-storage-context": "1.161.3",
"seroval": "^1.4.2",
"tiny-invariant": "^1.3.3",
"tiny-warning": "^1.0.3"
@@ -4732,27 +4775,27 @@
}
},
"node_modules/@tanstack/start-plugin-core": {
- "version": "1.161.1",
- "resolved": "https://registry.npmjs.org/@tanstack/start-plugin-core/-/start-plugin-core-1.161.1.tgz",
- "integrity": "sha512-BmrVIwtUUT7xuL2KOx1Es0x2ekhP0ga43nDSnAbQK0R9AeFFZVQwAtMinp21VuS8je77dWTie6wjLuUuUgoWng==",
+ "version": "1.161.3",
+ "resolved": "https://registry.npmjs.org/@tanstack/start-plugin-core/-/start-plugin-core-1.161.3.tgz",
+ "integrity": "sha512-HrxDuh5nn1F4LhJyQ1cHwou1VdlOsH3uOK/EEQXYBPpo+NKWGeaw06ff9fwmH6/FkpgWrh1c+4T0V1BS+T/YJA==",
"license": "MIT",
"dependencies": {
"@babel/code-frame": "7.27.1",
"@babel/core": "^7.28.5",
"@babel/types": "^7.28.5",
"@rolldown/pluginutils": "1.0.0-beta.40",
- "@tanstack/router-core": "1.161.1",
- "@tanstack/router-generator": "1.161.1",
- "@tanstack/router-plugin": "1.161.1",
+ "@tanstack/router-core": "1.161.3",
+ "@tanstack/router-generator": "1.161.3",
+ "@tanstack/router-plugin": "1.161.3",
"@tanstack/router-utils": "1.158.0",
- "@tanstack/start-client-core": "1.161.1",
- "@tanstack/start-server-core": "1.161.1",
+ "@tanstack/start-client-core": "1.161.3",
+ "@tanstack/start-server-core": "1.161.3",
"cheerio": "^1.0.0",
"exsolve": "^1.0.7",
"pathe": "^2.0.3",
"picomatch": "^4.0.3",
"source-map": "^0.7.6",
- "srvx": "^0.11.2",
+ "srvx": "^0.11.7",
"tinyglobby": "^0.2.15",
"ufo": "^1.5.4",
"vitefu": "^1.1.1",
@@ -4794,15 +4837,15 @@
}
},
"node_modules/@tanstack/start-server-core": {
- "version": "1.161.1",
- "resolved": "https://registry.npmjs.org/@tanstack/start-server-core/-/start-server-core-1.161.1.tgz",
- "integrity": "sha512-C0gMPzzjGD2Tg+Iqxrx8ztk/82uwdcBxqJ3yXVFXoJ797rzM6C+i+WMt87JSlRPLLp2EPlgilSAF2RMo2UQoWA==",
+ "version": "1.161.3",
+ "resolved": "https://registry.npmjs.org/@tanstack/start-server-core/-/start-server-core-1.161.3.tgz",
+ "integrity": "sha512-+/1XUd8Dvf462/OHsJJEhFZhERqd0iI/1JD9SyNPSqP6cFnkhIqIK+yaiD6ziQaGO+pkBqSmuRB2/acJg4NRTg==",
"license": "MIT",
"dependencies": {
"@tanstack/history": "1.154.14",
- "@tanstack/router-core": "1.161.1",
- "@tanstack/start-client-core": "1.161.1",
- "@tanstack/start-storage-context": "1.161.1",
+ "@tanstack/router-core": "1.161.3",
+ "@tanstack/start-client-core": "1.161.3",
+ "@tanstack/start-storage-context": "1.161.3",
"h3-v2": "npm:h3@2.0.1-rc.14",
"seroval": "^1.4.2",
"tiny-invariant": "^1.3.3"
@@ -4816,12 +4859,12 @@
}
},
"node_modules/@tanstack/start-storage-context": {
- "version": "1.161.1",
- "resolved": "https://registry.npmjs.org/@tanstack/start-storage-context/-/start-storage-context-1.161.1.tgz",
- "integrity": "sha512-dkBD5y5DwCwSmKgCgefv4zdee6gSDwqdgDF0wYIHxc5VprBFczmSjt0giMXq+Bx38C8nxR+aCPZr/SwoyMcFpA==",
+ "version": "1.161.3",
+ "resolved": "https://registry.npmjs.org/@tanstack/start-storage-context/-/start-storage-context-1.161.3.tgz",
+ "integrity": "sha512-X89oEykLrrhIn+41Q3jXVYRsg9NirM+7Nr0FLajFRle3FpAYggHq6TS8XPRhrv664uLa5Dz225sxCDwC5OT+sQ==",
"license": "MIT",
"dependencies": {
- "@tanstack/router-core": "1.161.1"
+ "@tanstack/router-core": "1.161.3"
},
"engines": {
"node": ">=22.12.0"
@@ -4832,9 +4875,9 @@
}
},
"node_modules/@tanstack/store": {
- "version": "0.8.1",
- "resolved": "https://registry.npmjs.org/@tanstack/store/-/store-0.8.1.tgz",
- "integrity": "sha512-PtOisLjUZPz5VyPRSCGjNOlwTvabdTBQ2K80DpVL1chGVr35WRxfeavAPdNq6pm/t7F8GhoR2qtmkkqtCEtHYw==",
+ "version": "0.9.1",
+ "resolved": "https://registry.npmjs.org/@tanstack/store/-/store-0.9.1.tgz",
+ "integrity": "sha512-+qcNkOy0N1qSGsP7omVCW0SDrXtaDcycPqBDE726yryiA5eTDFpjBReaYjghVJwNf1pcPMyzIwTGlYjCSQR0Fg==",
"license": "MIT",
"funding": {
"type": "github",
@@ -5009,13 +5052,13 @@
"license": "MIT"
},
"node_modules/@types/node": {
- "version": "22.19.11",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.11.tgz",
- "integrity": "sha512-BH7YwL6rA93ReqeQS1c4bsPpcfOmJasG+Fkr6Y59q83f9M1WcBRHR2vM+P9eOisYRcN3ujQoiZY8uk5W+1WL8w==",
+ "version": "25.3.0",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.0.tgz",
+ "integrity": "sha512-4K3bqJpXpqfg2XKGK9bpDTc6xO/xoUP/RBWS7AtRMug6zZFaRekiLzjVtAoZMquxoAbzBvy5nxQ7veS5eYzf8A==",
"devOptional": true,
"license": "MIT",
"dependencies": {
- "undici-types": "~6.21.0"
+ "undici-types": "~7.18.0"
}
},
"node_modules/@types/prop-types": {
@@ -5237,32 +5280,6 @@
"typescript": ">=4.8.4 <6.0.0"
}
},
- "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
- "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0"
- }
- },
- "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
- "version": "9.0.5",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
- "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "brace-expansion": "^2.0.1"
- },
- "engines": {
- "node": ">=16 || 14 >=14.17"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
"node_modules/@typescript-eslint/typescript-estree/node_modules/semver": {
"version": "7.7.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
@@ -5642,39 +5659,40 @@
"license": "MIT"
},
"node_modules/@vitest/expect": {
- "version": "3.2.4",
- "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz",
- "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==",
+ "version": "4.0.18",
+ "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.18.tgz",
+ "integrity": "sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==",
"dev": true,
"license": "MIT",
"dependencies": {
+ "@standard-schema/spec": "^1.0.0",
"@types/chai": "^5.2.2",
- "@vitest/spy": "3.2.4",
- "@vitest/utils": "3.2.4",
- "chai": "^5.2.0",
- "tinyrainbow": "^2.0.0"
+ "@vitest/spy": "4.0.18",
+ "@vitest/utils": "4.0.18",
+ "chai": "^6.2.1",
+ "tinyrainbow": "^3.0.3"
},
"funding": {
"url": "https://opencollective.com/vitest"
}
},
"node_modules/@vitest/mocker": {
- "version": "3.2.4",
- "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz",
- "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==",
+ "version": "4.0.18",
+ "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.18.tgz",
+ "integrity": "sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/spy": "3.2.4",
+ "@vitest/spy": "4.0.18",
"estree-walker": "^3.0.3",
- "magic-string": "^0.30.17"
+ "magic-string": "^0.30.21"
},
"funding": {
"url": "https://opencollective.com/vitest"
},
"peerDependencies": {
"msw": "^2.4.9",
- "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0"
+ "vite": "^6.0.0 || ^7.0.0-0"
},
"peerDependenciesMeta": {
"msw": {
@@ -5686,42 +5704,41 @@
}
},
"node_modules/@vitest/pretty-format": {
- "version": "3.2.4",
- "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz",
- "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==",
+ "version": "4.0.18",
+ "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.18.tgz",
+ "integrity": "sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "tinyrainbow": "^2.0.0"
+ "tinyrainbow": "^3.0.3"
},
"funding": {
"url": "https://opencollective.com/vitest"
}
},
"node_modules/@vitest/runner": {
- "version": "3.2.4",
- "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz",
- "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==",
+ "version": "4.0.18",
+ "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.18.tgz",
+ "integrity": "sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/utils": "3.2.4",
- "pathe": "^2.0.3",
- "strip-literal": "^3.0.0"
+ "@vitest/utils": "4.0.18",
+ "pathe": "^2.0.3"
},
"funding": {
"url": "https://opencollective.com/vitest"
}
},
"node_modules/@vitest/snapshot": {
- "version": "3.2.4",
- "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz",
- "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==",
+ "version": "4.0.18",
+ "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.18.tgz",
+ "integrity": "sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/pretty-format": "3.2.4",
- "magic-string": "^0.30.17",
+ "@vitest/pretty-format": "4.0.18",
+ "magic-string": "^0.30.21",
"pathe": "^2.0.3"
},
"funding": {
@@ -5729,28 +5746,24 @@
}
},
"node_modules/@vitest/spy": {
- "version": "3.2.4",
- "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz",
- "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==",
+ "version": "4.0.18",
+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.18.tgz",
+ "integrity": "sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "tinyspy": "^4.0.3"
- },
"funding": {
"url": "https://opencollective.com/vitest"
}
},
"node_modules/@vitest/utils": {
- "version": "3.2.4",
- "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz",
- "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==",
+ "version": "4.0.18",
+ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.18.tgz",
+ "integrity": "sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/pretty-format": "3.2.4",
- "loupe": "^3.1.4",
- "tinyrainbow": "^2.0.0"
+ "@vitest/pretty-format": "4.0.18",
+ "tinyrainbow": "^3.0.3"
},
"funding": {
"url": "https://opencollective.com/vitest"
@@ -5788,24 +5801,6 @@
"node": ">= 14"
}
},
- "node_modules/ajv": {
- "version": "6.12.6",
- "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
- "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
- "dev": true,
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "fast-deep-equal": "^3.1.1",
- "fast-json-stable-stringify": "^2.0.0",
- "json-schema-traverse": "^0.4.1",
- "uri-js": "^4.2.2"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/epoberezkin"
- }
- },
"node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
@@ -5933,11 +5928,14 @@
}
},
"node_modules/balanced-match": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
- "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.3.tgz",
+ "integrity": "sha512-1pHv8LX9CpKut1Zp4EXey7Z8OfH11ONNH6Dhi2WDUt31VVZFXZzKwXcysBgqSumFCmR+0dqjMK5v5JiFHzi0+g==",
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "engines": {
+ "node": "20 || >=22"
+ }
},
"node_modules/base16": {
"version": "1.0.0",
@@ -5983,15 +5981,16 @@
"license": "ISC"
},
"node_modules/brace-expansion": {
- "version": "1.1.12",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
- "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.2.tgz",
+ "integrity": "sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
+ "balanced-match": "^4.0.2"
+ },
+ "engines": {
+ "node": "20 || >=22"
}
},
"node_modules/braces": {
@@ -6039,16 +6038,6 @@
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
}
},
- "node_modules/cac": {
- "version": "6.7.14",
- "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
- "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/callsites": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
@@ -6081,18 +6070,11 @@
"license": "CC-BY-4.0"
},
"node_modules/chai": {
- "version": "5.3.3",
- "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz",
- "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==",
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz",
+ "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "assertion-error": "^2.0.1",
- "check-error": "^2.1.1",
- "deep-eql": "^5.0.1",
- "loupe": "^3.1.0",
- "pathval": "^2.0.0"
- },
"engines": {
"node": ">=18"
}
@@ -6110,16 +6092,6 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
- "node_modules/check-error": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz",
- "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 16"
- }
- },
"node_modules/cheerio": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.2.0.tgz",
@@ -6221,7 +6193,9 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"color-name": "~1.1.4"
},
@@ -6295,14 +6269,6 @@
"node": ">= 12.0.0"
}
},
- "node_modules/concat-map": {
- "version": "0.0.1",
- "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
- "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
- "dev": true,
- "license": "MIT",
- "peer": true
- },
"node_modules/consola": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/consola/-/consola-3.4.0.tgz",
@@ -6460,16 +6426,16 @@
}
},
"node_modules/cssstyle": {
- "version": "5.3.7",
- "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-5.3.7.tgz",
- "integrity": "sha512-7D2EPVltRrsTkhpQmksIu+LxeWAIEk6wRDMJ1qljlv+CKHJM+cJLlfhWIzNA44eAsHXSNe3+vO6DW1yCYx8SuQ==",
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-6.0.1.tgz",
+ "integrity": "sha512-IoJs7La+oFp/AB033wBStxNOJt4+9hHMxsXUPANcoXL2b3W4DZKghlJ2cI/eyeRZIQ9ysvYEorVhjrcYctWbog==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@asamuzakjp/css-color": "^4.1.1",
- "@csstools/css-syntax-patches-for-csstree": "^1.0.21",
+ "@asamuzakjp/css-color": "^4.1.2",
+ "@csstools/css-syntax-patches-for-csstree": "^1.0.26",
"css-tree": "^3.1.0",
- "lru-cache": "^11.2.4"
+ "lru-cache": "^11.2.5"
},
"engines": {
"node": ">=20"
@@ -6492,17 +6458,17 @@
"license": "MIT"
},
"node_modules/data-urls": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-6.0.1.tgz",
- "integrity": "sha512-euIQENZg6x8mj3fO6o9+fOW8MimUI4PpD/fZBhJfeioZVy9TUpM4UY7KjQNVZFlqwJ0UdzRDzkycB997HEq1BQ==",
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz",
+ "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==",
"dev": true,
"license": "MIT",
"dependencies": {
"whatwg-mimetype": "^5.0.0",
- "whatwg-url": "^15.1.0"
+ "whatwg-url": "^16.0.0"
},
"engines": {
- "node": ">=20"
+ "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
}
},
"node_modules/data-urls/node_modules/whatwg-mimetype": {
@@ -6588,16 +6554,6 @@
}
}
},
- "node_modules/deep-eql": {
- "version": "5.0.2",
- "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz",
- "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
"node_modules/deep-is": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
@@ -6639,12 +6595,6 @@
"node": ">=0.3.1"
}
},
- "node_modules/diff-match-patch": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz",
- "integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==",
- "license": "Apache-2.0"
- },
"node_modules/dom-accessibility-api": {
"version": "0.5.16",
"resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
@@ -6823,9 +6773,9 @@
}
},
"node_modules/eslint": {
- "version": "9.39.2",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz",
- "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
+ "version": "9.39.3",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.3.tgz",
+ "integrity": "sha512-VmQ+sifHUbI/IcSopBCF/HO3YiHQx/AVd3UVyYL6weuwW+HvON9VYn5l6Zl1WZzPWXPNZrSQpxwkkZ/VuvJZzg==",
"dev": true,
"license": "MIT",
"peer": true,
@@ -6836,7 +6786,7 @@
"@eslint/config-helpers": "^0.4.2",
"@eslint/core": "^0.17.0",
"@eslint/eslintrc": "^3.3.1",
- "@eslint/js": "9.39.2",
+ "@eslint/js": "9.39.3",
"@eslint/plugin-kit": "^0.4.1",
"@humanfs/node": "^0.16.6",
"@humanwhocodes/module-importer": "^1.0.1",
@@ -6996,45 +6946,6 @@
}
}
},
- "node_modules/eslint-plugin-import-x/node_modules/balanced-match": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.3.tgz",
- "integrity": "sha512-1pHv8LX9CpKut1Zp4EXey7Z8OfH11ONNH6Dhi2WDUt31VVZFXZzKwXcysBgqSumFCmR+0dqjMK5v5JiFHzi0+g==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "20 || >=22"
- }
- },
- "node_modules/eslint-plugin-import-x/node_modules/brace-expansion": {
- "version": "5.0.2",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.2.tgz",
- "integrity": "sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^4.0.2"
- },
- "engines": {
- "node": "20 || >=22"
- }
- },
- "node_modules/eslint-plugin-import-x/node_modules/minimatch": {
- "version": "10.2.1",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.1.tgz",
- "integrity": "sha512-MClCe8IL5nRRmawL6ib/eT4oLyeKMGCghibcDWK+J0hh0Q8kqSdia6BvbRMVk6mPa6WqUa5uR2oxt6C5jd533A==",
- "dev": true,
- "license": "BlueOak-1.0.0",
- "dependencies": {
- "brace-expansion": "^5.0.2"
- },
- "engines": {
- "node": "20 || >=22"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
"node_modules/eslint-plugin-import-x/node_modules/semver": {
"version": "7.7.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
@@ -7131,6 +7042,24 @@
"url": "https://opencollective.com/eslint"
}
},
+ "node_modules/eslint/node_modules/ajv": {
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
"node_modules/eslint/node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
@@ -7180,6 +7109,14 @@
"node": ">=10.13.0"
}
},
+ "node_modules/eslint/node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
"node_modules/espree": {
"version": "10.4.0",
"resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
@@ -7455,9 +7392,9 @@
"license": "BSD-2-Clause"
},
"node_modules/globals": {
- "version": "16.5.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz",
- "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==",
+ "version": "17.3.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-17.3.0.tgz",
+ "integrity": "sha512-yMqGUQVVCkD4tqjOJf3TnrvaaHDMYp4VlUSObbkIiuCPe/ofdMBFIAcBbCSRFWOnos6qRiTVStDwqPLUclaxIw==",
"dev": true,
"license": "MIT",
"engines": {
@@ -7541,7 +7478,9 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=8"
}
@@ -7938,17 +7877,18 @@
}
},
"node_modules/jsdom": {
- "version": "27.4.0",
- "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-27.4.0.tgz",
- "integrity": "sha512-mjzqwWRD9Y1J1KUi7W97Gja1bwOOM5Ug0EZ6UDK3xS7j7mndrkwozHtSblfomlzyB4NepioNt+B2sOSzczVgtQ==",
+ "version": "28.1.0",
+ "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-28.1.0.tgz",
+ "integrity": "sha512-0+MoQNYyr2rBHqO1xilltfDjV9G7ymYGlAUazgcDLQaUf8JDHbuGwsxN6U9qWaElZ4w1B2r7yEGIL3GdeW3Rug==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@acemir/cssom": "^0.9.28",
- "@asamuzakjp/dom-selector": "^6.7.6",
- "@exodus/bytes": "^1.6.0",
- "cssstyle": "^5.3.4",
- "data-urls": "^6.0.0",
+ "@acemir/cssom": "^0.9.31",
+ "@asamuzakjp/dom-selector": "^6.8.1",
+ "@bramus/specificity": "^2.4.2",
+ "@exodus/bytes": "^1.11.0",
+ "cssstyle": "^6.0.1",
+ "data-urls": "^7.0.0",
"decimal.js": "^10.6.0",
"html-encoding-sniffer": "^6.0.0",
"http-proxy-agent": "^7.0.2",
@@ -7958,11 +7898,11 @@
"saxes": "^6.0.0",
"symbol-tree": "^3.2.4",
"tough-cookie": "^6.0.0",
+ "undici": "^7.21.0",
"w3c-xmlserializer": "^5.0.0",
- "webidl-conversions": "^8.0.0",
- "whatwg-mimetype": "^4.0.0",
- "whatwg-url": "^15.1.0",
- "ws": "^8.18.3",
+ "webidl-conversions": "^8.0.1",
+ "whatwg-mimetype": "^5.0.0",
+ "whatwg-url": "^16.0.0",
"xml-name-validator": "^5.0.0"
},
"engines": {
@@ -8003,6 +7943,16 @@
"url": "https://github.com/inikulin/parse5?sponsor=1"
}
},
+ "node_modules/jsdom/node_modules/whatwg-mimetype": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz",
+ "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=20"
+ }
+ },
"node_modules/jsesc": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
@@ -8023,14 +7973,6 @@
"license": "MIT",
"peer": true
},
- "node_modules/json-schema-traverse": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
- "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
- "dev": true,
- "license": "MIT",
- "peer": true
- },
"node_modules/json-stable-stringify-without-jsonify": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
@@ -8052,47 +7994,18 @@
}
},
"node_modules/jsondiffpatch": {
- "version": "0.5.0",
- "resolved": "https://registry.npmjs.org/jsondiffpatch/-/jsondiffpatch-0.5.0.tgz",
- "integrity": "sha512-Quz3MvAwHxVYNXsOByL7xI5EB2WYOeFswqaHIA3qOK3isRWTxiplBEocmmru6XmxDB2L7jDNYtYA4FyimoAFEw==",
+ "version": "0.7.3",
+ "resolved": "https://registry.npmjs.org/jsondiffpatch/-/jsondiffpatch-0.7.3.tgz",
+ "integrity": "sha512-zd4dqFiXSYyant2WgSXAZ9+yYqilNVvragVNkNRn2IFZKgjyULNrKRznqN4Zon0MkLueCg+3QaPVCnDAVP20OQ==",
"license": "MIT",
"dependencies": {
- "chalk": "^3.0.0",
- "diff-match-patch": "^1.0.0"
+ "@dmsnell/diff-match-patch": "^1.1.0"
},
"bin": {
- "jsondiffpatch": "bin/jsondiffpatch"
- },
- "engines": {
- "node": ">=8.17.0"
- }
- },
- "node_modules/jsondiffpatch/node_modules/ansi-styles": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
- "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
- "license": "MIT",
- "dependencies": {
- "color-convert": "^2.0.1"
+ "jsondiffpatch": "bin/jsondiffpatch.js"
},
"engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
- "node_modules/jsondiffpatch/node_modules/chalk": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
- "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
- "license": "MIT",
- "dependencies": {
- "ansi-styles": "^4.1.0",
- "supports-color": "^7.1.0"
- },
- "engines": {
- "node": ">=8"
+ "node": "^18.0.0 || >=20.0.0"
}
},
"node_modules/keyv": {
@@ -8422,13 +8335,6 @@
"license": "MIT",
"peer": true
},
- "node_modules/loupe": {
- "version": "3.2.1",
- "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz",
- "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/lru-cache": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
@@ -8439,9 +8345,9 @@
}
},
"node_modules/lucide-react": {
- "version": "0.561.0",
- "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.561.0.tgz",
- "integrity": "sha512-Y59gMY38tl4/i0qewcqohPdEbieBy7SovpBL9IFebhc2mDd8x4PZSOsiFRkpPcOq6bj1r/mjH/Rk73gSlIJP2A==",
+ "version": "0.575.0",
+ "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.575.0.tgz",
+ "integrity": "sha512-VuXgKZrk0uiDlWjGGXmKV6MSk9Yy4l10qgVvzGn2AWBx1Ylt0iBexKOAoA6I7JO3m+M9oeovJd3yYENfkUbOeg==",
"license": "ISC",
"peerDependencies": {
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
@@ -8474,17 +8380,19 @@
"license": "CC0-1.0"
},
"node_modules/minimatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "version": "10.2.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.2.tgz",
+ "integrity": "sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw==",
"dev": true,
- "license": "ISC",
- "peer": true,
+ "license": "BlueOak-1.0.0",
"dependencies": {
- "brace-expansion": "^1.1.7"
+ "brace-expansion": "^5.0.2"
},
"engines": {
- "node": "*"
+ "node": "18 || 20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/ms": {
@@ -8733,6 +8641,17 @@
"url": "https://github.com/fb55/nth-check?sponsor=1"
}
},
+ "node_modules/obug": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz",
+ "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==",
+ "dev": true,
+ "funding": [
+ "https://github.com/sponsors/sxzz",
+ "https://opencollective.com/debug"
+ ],
+ "license": "MIT"
+ },
"node_modules/ofetch": {
"version": "2.0.0-alpha.3",
"resolved": "https://registry.npmjs.org/ofetch/-/ofetch-2.0.0-alpha.3.tgz",
@@ -8898,16 +8817,6 @@
"integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
"license": "MIT"
},
- "node_modules/pathval": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz",
- "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 14.16"
- }
- },
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@@ -9577,9 +9486,9 @@
}
},
"node_modules/srvx": {
- "version": "0.11.5",
- "resolved": "https://registry.npmjs.org/srvx/-/srvx-0.11.5.tgz",
- "integrity": "sha512-MbQgu/gbLcXjg1bhUhPXXOpeMfmDMTGSKPWeht5acXnlQNldD925eS4+bIH/qESecSkP71dU3Fmvunlai1+yzw==",
+ "version": "0.11.7",
+ "resolved": "https://registry.npmjs.org/srvx/-/srvx-0.11.7.tgz",
+ "integrity": "sha512-p9qj9wkv/MqG1VoJpOsqXv1QcaVcYRk7ifsC6i3TEwDXFyugdhJN4J3KzQPZq2IJJ2ZCt7ASOB++85pEK38jRw==",
"license": "MIT",
"bin": {
"srvx": "bin/srvx.mjs"
@@ -9625,31 +9534,13 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/strip-literal": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz",
- "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "js-tokens": "^9.0.1"
- },
- "funding": {
- "url": "https://github.com/sponsors/antfu"
- }
- },
- "node_modules/strip-literal/node_modules/js-tokens": {
- "version": "9.0.1",
- "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz",
- "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"has-flag": "^4.0.0"
},
@@ -9684,9 +9575,9 @@
"license": "MIT"
},
"node_modules/tailwind-merge": {
- "version": "3.4.1",
- "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.4.1.tgz",
- "integrity": "sha512-2OA0rFqWOkITEAOFWSBSApYkDeH9t2B3XSJuI4YztKBzK3mX0737A2qtxDZ7xkw9Zfh0bWl+r34sF3HXV+Ig7Q==",
+ "version": "3.5.0",
+ "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.5.0.tgz",
+ "integrity": "sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==",
"license": "MIT",
"funding": {
"type": "github",
@@ -9732,11 +9623,14 @@
"license": "MIT"
},
"node_modules/tinyexec": {
- "version": "0.3.2",
- "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz",
- "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==",
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz",
+ "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==",
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
},
"node_modules/tinyglobby": {
"version": "0.2.15",
@@ -9754,30 +9648,10 @@
"url": "https://github.com/sponsors/SuperchupuDev"
}
},
- "node_modules/tinypool": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz",
- "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "^18.0.0 || >=20.0.0"
- }
- },
"node_modules/tinyrainbow": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz",
- "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/tinyspy": {
- "version": "4.0.4",
- "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.4.tgz",
- "integrity": "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==",
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz",
+ "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==",
"dev": true,
"license": "MIT",
"engines": {
@@ -10012,9 +9886,9 @@
}
},
"node_modules/undici-types": {
- "version": "6.21.0",
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
- "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
+ "version": "7.18.2",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
+ "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
"devOptional": true,
"license": "MIT"
},
@@ -10310,33 +10184,10 @@
}
}
},
- "node_modules/vite-node": {
- "version": "3.2.4",
- "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz",
- "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "cac": "^6.7.14",
- "debug": "^4.4.1",
- "es-module-lexer": "^1.7.0",
- "pathe": "^2.0.3",
- "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0"
- },
- "bin": {
- "vite-node": "vite-node.mjs"
- },
- "engines": {
- "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/vitest"
- }
- },
"node_modules/vite-tsconfig-paths": {
- "version": "5.1.4",
- "resolved": "https://registry.npmjs.org/vite-tsconfig-paths/-/vite-tsconfig-paths-5.1.4.tgz",
- "integrity": "sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w==",
+ "version": "6.1.1",
+ "resolved": "https://registry.npmjs.org/vite-tsconfig-paths/-/vite-tsconfig-paths-6.1.1.tgz",
+ "integrity": "sha512-2cihq7zliibCCZ8P9cKJrQBkfgdvcFkOOc3Y02o3GWUDLgqjWsZudaoiuOwO/gzTzy17cS5F7ZPo4bsnS4DGkg==",
"license": "MIT",
"dependencies": {
"debug": "^4.1.1",
@@ -10345,11 +10196,6 @@
},
"peerDependencies": {
"vite": "*"
- },
- "peerDependenciesMeta": {
- "vite": {
- "optional": true
- }
}
},
"node_modules/vitefu": {
@@ -10372,51 +10218,50 @@
}
},
"node_modules/vitest": {
- "version": "3.2.4",
- "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz",
- "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==",
+ "version": "4.0.18",
+ "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.18.tgz",
+ "integrity": "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@types/chai": "^5.2.2",
- "@vitest/expect": "3.2.4",
- "@vitest/mocker": "3.2.4",
- "@vitest/pretty-format": "^3.2.4",
- "@vitest/runner": "3.2.4",
- "@vitest/snapshot": "3.2.4",
- "@vitest/spy": "3.2.4",
- "@vitest/utils": "3.2.4",
- "chai": "^5.2.0",
- "debug": "^4.4.1",
- "expect-type": "^1.2.1",
- "magic-string": "^0.30.17",
+ "@vitest/expect": "4.0.18",
+ "@vitest/mocker": "4.0.18",
+ "@vitest/pretty-format": "4.0.18",
+ "@vitest/runner": "4.0.18",
+ "@vitest/snapshot": "4.0.18",
+ "@vitest/spy": "4.0.18",
+ "@vitest/utils": "4.0.18",
+ "es-module-lexer": "^1.7.0",
+ "expect-type": "^1.2.2",
+ "magic-string": "^0.30.21",
+ "obug": "^2.1.1",
"pathe": "^2.0.3",
- "picomatch": "^4.0.2",
- "std-env": "^3.9.0",
+ "picomatch": "^4.0.3",
+ "std-env": "^3.10.0",
"tinybench": "^2.9.0",
- "tinyexec": "^0.3.2",
- "tinyglobby": "^0.2.14",
- "tinypool": "^1.1.1",
- "tinyrainbow": "^2.0.0",
- "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0",
- "vite-node": "3.2.4",
+ "tinyexec": "^1.0.2",
+ "tinyglobby": "^0.2.15",
+ "tinyrainbow": "^3.0.3",
+ "vite": "^6.0.0 || ^7.0.0",
"why-is-node-running": "^2.3.0"
},
"bin": {
"vitest": "vitest.mjs"
},
"engines": {
- "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
+ "node": "^20.0.0 || ^22.0.0 || >=24.0.0"
},
"funding": {
"url": "https://opencollective.com/vitest"
},
"peerDependencies": {
"@edge-runtime/vm": "*",
- "@types/debug": "^4.1.12",
- "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
- "@vitest/browser": "3.2.4",
- "@vitest/ui": "3.2.4",
+ "@opentelemetry/api": "^1.9.0",
+ "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0",
+ "@vitest/browser-playwright": "4.0.18",
+ "@vitest/browser-preview": "4.0.18",
+ "@vitest/browser-webdriverio": "4.0.18",
+ "@vitest/ui": "4.0.18",
"happy-dom": "*",
"jsdom": "*"
},
@@ -10424,13 +10269,19 @@
"@edge-runtime/vm": {
"optional": true
},
- "@types/debug": {
+ "@opentelemetry/api": {
"optional": true
},
"@types/node": {
"optional": true
},
- "@vitest/browser": {
+ "@vitest/browser-playwright": {
+ "optional": true
+ },
+ "@vitest/browser-preview": {
+ "optional": true
+ },
+ "@vitest/browser-webdriverio": {
"optional": true
},
"@vitest/ui": {
@@ -10533,17 +10384,18 @@
}
},
"node_modules/whatwg-url": {
- "version": "15.1.0",
- "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-15.1.0.tgz",
- "integrity": "sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==",
+ "version": "16.0.1",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz",
+ "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==",
"dev": true,
"license": "MIT",
"dependencies": {
+ "@exodus/bytes": "^1.11.0",
"tr46": "^6.0.0",
- "webidl-conversions": "^8.0.0"
+ "webidl-conversions": "^8.0.1"
},
"engines": {
- "node": ">=20"
+ "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
}
},
"node_modules/which": {
diff --git a/package.json b/package.json
index 64927f5..33f61e1 100644
--- a/package.json
+++ b/package.json
@@ -15,55 +15,59 @@
"db:start": "npx convex dev"
},
"dependencies": {
- "@clerk/clerk-react": "^5.49.0",
+ "@clerk/clerk-react": "^5.61.0",
"@convex-dev/react-query": "0.1.0",
"@scure/bip39": "^2.0.1",
- "@t3-oss/env-core": "^0.13.8",
- "@tailwindcss/vite": "^4.1.18",
- "@tanstack/react-devtools": "^0.7.0",
- "@tanstack/react-query": "^5.66.5",
- "@tanstack/react-query-devtools": "^5.84.2",
- "@tanstack/react-router": "^1.132.0",
- "@tanstack/react-router-devtools": "^1.132.0",
- "@tanstack/react-router-ssr-query": "^1.131.7",
- "@tanstack/react-start": "^1.132.0",
- "@tanstack/react-store": "^0.8.0",
- "@tanstack/router-plugin": "^1.132.0",
- "@tanstack/store": "^0.8.0",
+ "@t3-oss/env-core": "^0.13.10",
+ "@tailwindcss/vite": "^4.2.0",
+ "@tanstack/react-devtools": "^0.9.6",
+ "@tanstack/react-query": "^5.90.21",
+ "@tanstack/react-query-devtools": "^5.91.3",
+ "@tanstack/react-router": "^1.161.3",
+ "@tanstack/react-router-devtools": "^1.161.3",
+ "@tanstack/react-router-ssr-query": "^1.161.3",
+ "@tanstack/react-start": "^1.161.3",
+ "@tanstack/react-store": "^0.9.1",
+ "@tanstack/router-plugin": "^1.161.3",
+ "@tanstack/store": "^0.9.1",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"convex": "^1.32.0",
"jotai": "^2.18.0",
"jotai-devtools": "^0.13.0",
- "lucide-react": "^0.561.0",
+ "lucide-react": "^0.575.0",
"nitro": "npm:nitro-nightly@latest",
"openpgp": "^6.3.0",
"radix-ui": "^1.4.3",
- "react": "^19.2.0",
- "react-dom": "^19.2.0",
- "tailwind-merge": "^3.0.2",
- "tailwindcss": "^4.1.18",
- "tw-animate-css": "^1.3.6",
- "vite-tsconfig-paths": "^5.1.4",
- "zod": "^4.1.11"
+ "react": "^19.2.4",
+ "react-dom": "^19.2.4",
+ "tailwind-merge": "^3.5.0",
+ "tailwindcss": "^4.2.0",
+ "tw-animate-css": "^1.4.0",
+ "vite-tsconfig-paths": "^6.1.1",
+ "zod": "^4.3.6"
},
"devDependencies": {
- "@inlang/paraglide-js": "^2.8.0",
+ "@inlang/paraglide-js": "^2.12.0",
"@tanstack/devtools-event-client": "^0.4.0",
- "@tanstack/devtools-vite": "^0.3.11",
- "@tanstack/eslint-config": "^0.3.0",
- "@testing-library/dom": "^10.4.0",
- "@testing-library/react": "^16.2.0",
- "@types/node": "^22.10.2",
- "@types/react": "^19.2.0",
- "@types/react-dom": "^19.2.0",
- "@vitejs/plugin-react": "^5.0.4",
+ "@tanstack/devtools-vite": "^0.5.1",
+ "@tanstack/eslint-config": "^0.4.0",
+ "@testing-library/dom": "^10.4.1",
+ "@testing-library/react": "^16.3.2",
+ "@types/node": "^25.3.0",
+ "@types/react": "^19.2.14",
+ "@types/react-dom": "^19.2.3",
+ "@vitejs/plugin-react": "^5.1.4",
"husky": "^9.1.7",
"jotai-babel": "^0.1.0",
- "jsdom": "^27.0.0",
- "prettier": "^3.5.3",
- "typescript": "^5.7.2",
- "vite": "^7.1.7",
- "vitest": "^3.0.5"
+ "jsdom": "^28.1.0",
+ "prettier": "^3.8.1",
+ "typescript": "^5.9.3",
+ "vite": "^7.3.1",
+ "vitest": "^4.0.18"
+ },
+ "overrides": {
+ "minimatch": "^10.2.1",
+ "jsondiffpatch": "^0.7.3"
}
}
diff --git a/src/__tests__/useSignIn.test.tsx b/src/__tests__/useSignIn.test.tsx
index 63a277a..dc2f972 100644
--- a/src/__tests__/useSignIn.test.tsx
+++ b/src/__tests__/useSignIn.test.tsx
@@ -77,7 +77,7 @@ describe('useSignIn', () => {
})
await act(async () => {
- result.current.handleSignIn({
+ await result.current.handleSignIn({
preventDefault: vi.fn(),
} as unknown as React.SubmitEvent)
})
@@ -104,7 +104,7 @@ describe('useSignIn', () => {
})
await act(async () => {
- result.current.handleSignIn({
+ await result.current.handleSignIn({
preventDefault: vi.fn(),
} as unknown as React.SubmitEvent)
})
@@ -128,7 +128,7 @@ describe('useSignIn', () => {
})
await act(async () => {
- result.current.handleSignIn({
+ await result.current.handleSignIn({
preventDefault: vi.fn(),
} as unknown as React.SubmitEvent)
})
@@ -143,7 +143,7 @@ describe('useSignIn', () => {
const { result } = renderHook(() => useSignIn())
await act(async () => {
- result.current.handleSignIn({
+ await result.current.handleSignIn({
preventDefault: vi.fn(),
} as unknown as React.SubmitEvent)
})
@@ -169,7 +169,7 @@ describe('useSignIn', () => {
})
await act(async () => {
- result.current.handleMfaVerify({
+ await result.current.handleMfaVerify({
preventDefault: vi.fn(),
} as unknown as React.SubmitEvent)
})
@@ -194,7 +194,7 @@ describe('useSignIn', () => {
})
await act(async () => {
- result.current.handleMfaVerify({
+ await result.current.handleMfaVerify({
preventDefault: vi.fn(),
} as unknown as React.SubmitEvent)
})
diff --git a/src/__tests__/useSignUp.test.tsx b/src/__tests__/useSignUp.test.tsx
index 9442886..d5220f0 100644
--- a/src/__tests__/useSignUp.test.tsx
+++ b/src/__tests__/useSignUp.test.tsx
@@ -82,7 +82,7 @@ describe('useSignUp', () => {
})
await act(async () => {
- result.current.handleSignUp({
+ await result.current.handleSignUp({
preventDefault: vi.fn(),
} as unknown as React.SubmitEvent)
})
@@ -110,7 +110,7 @@ describe('useSignUp', () => {
})
await act(async () => {
- result.current.handleVerification({
+ await result.current.handleVerification({
preventDefault: vi.fn(),
} as unknown as React.SubmitEvent)
})
@@ -141,7 +141,7 @@ describe('useSignUp', () => {
})
await act(async () => {
- result.current.handleVerification({
+ await result.current.handleVerification({
preventDefault: vi.fn(),
} as unknown as React.SubmitEvent)
})
@@ -162,7 +162,7 @@ describe('useSignUp', () => {
})
await act(async () => {
- result.current.handleSignUp({
+ await result.current.handleSignUp({
preventDefault: vi.fn(),
} as unknown as React.SubmitEvent)
})
From 12017153605532cc39796f0b3b2781e7aa1cacf2 Mon Sep 17 00:00:00 2001
From: jslyonnais <6282845+jslyonnais@users.noreply.github.com>
Date: Fri, 20 Feb 2026 12:48:45 -0500
Subject: [PATCH 19/19] vitest run
---
package.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/package.json b/package.json
index 33f61e1..974bdf6 100644
--- a/package.json
+++ b/package.json
@@ -7,7 +7,7 @@
"dev": "vite dev --port 3666",
"build": "vite build",
"preview": "vite preview",
- "test": "NODE_OPTIONS='--no-webstorage' vitest run",
+ "test": "vitest run",
"lint": "eslint",
"format": "prettier --check .",
"validate": "prettier --write . && eslint --fix",