diff --git a/convex/_generated/api.d.ts b/convex/_generated/api.d.ts index de319a0..bcf5f1f 100644 --- a/convex/_generated/api.d.ts +++ b/convex/_generated/api.d.ts @@ -8,29 +8,42 @@ * @module */ +import type * as userKeys from '../userKeys.js' + import type { ApiFromModules, FilterApi, FunctionReference, } from 'convex/server' -import type * as todos from '../todos.js' + +declare const fullApi: ApiFromModules<{ + userKeys: typeof userKeys +}> /** - * A utility for referencing Convex functions in your app's API. + * A utility for referencing Convex functions in your app's public API. * * Usage: * ```js * const myFunctionReference = api.myModule.myFunction; * ``` */ -declare const fullApi: ApiFromModules<{ - todos: typeof todos -}> export declare const api: FilterApi< typeof fullApi, FunctionReference > + +/** + * A utility for referencing Convex functions in your app's internal API. + * + * Usage: + * ```js + * const myFunctionReference = internal.myModule.myFunction; + * ``` + */ export declare const internal: FilterApi< typeof fullApi, FunctionReference > + +export declare const components: {} diff --git a/convex/_generated/api.js b/convex/_generated/api.js index 2b62e6b..738fe4e 100644 --- a/convex/_generated/api.js +++ b/convex/_generated/api.js @@ -8,7 +8,7 @@ * @module */ -import { anyApi } from 'convex/server' +import { anyApi, componentsGeneric } from 'convex/server' /** * A utility for referencing Convex functions in your app's API. @@ -20,3 +20,4 @@ import { anyApi } from 'convex/server' */ 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 5b936a4..d5b5a3b 100644 --- a/convex/_generated/dataModel.d.ts +++ b/convex/_generated/dataModel.d.ts @@ -38,7 +38,7 @@ export type Doc = DocumentByName< * Convex documents are uniquely identified by their `Id`, which is accessible * on the `_id` field. To learn more, see [Document IDs](https://docs.convex.dev/using/document-ids). * - * Documents can be loaded using `db.get(id)` in query and mutation functions. + * Documents can be loaded using `db.get(tableName, id)` in query and mutation functions. * * IDs are just strings at runtime, but this type can be used to distinguish them from other * strings when type checking. diff --git a/convex/_generated/server.d.ts b/convex/_generated/server.d.ts index 869a677..ccd5e20 100644 --- a/convex/_generated/server.d.ts +++ b/convex/_generated/server.d.ts @@ -85,11 +85,12 @@ export declare const internalAction: ActionBuilder /** * Define an HTTP action. * - * This function will be used to respond to HTTP requests received by a Convex - * deployment if the requests matches the path and method where this action - * is routed. Be sure to route your action in `convex/http.js`. + * The wrapped function will be used to respond to HTTP requests received + * by a Convex deployment if the requests matches the path and method where + * this action is routed. Be sure to route your httpAction in `convex/http.js`. * - * @param func - The function. It receives an {@link ActionCtx} as its first argument. + * @param func - The function. It receives an {@link ActionCtx} as its first argument + * 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 diff --git a/convex/_generated/server.js b/convex/_generated/server.js index d16f952..682fcd2 100644 --- a/convex/_generated/server.js +++ b/convex/_generated/server.js @@ -80,10 +80,14 @@ export const action = actionGeneric export const internalAction = internalActionGeneric /** - * Define a Convex HTTP action. + * Define an HTTP action. * - * @param func - The function. It receives an {@link ActionCtx} as its first argument, and a `Request` object - * as its second. - * @returns The wrapped endpoint function. Route a URL path to this function in `convex/http.js`. + * The wrapped function will be used to respond to HTTP requests received + * by a Convex deployment if the requests matches the path and method where + * this action is routed. Be sure to route your httpAction in `convex/http.js`. + * + * @param func - The function. It receives an {@link ActionCtx} as its first argument + * 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 diff --git a/convex/crons.ts b/convex/crons.ts deleted file mode 100644 index 36b9a3e..0000000 --- a/convex/crons.ts +++ /dev/null @@ -1,14 +0,0 @@ -// convex/crons.ts -import { cronJobs } from 'convex/server' -import { internal } from './_generated/api' - -const crons = cronJobs() - -// Clean up expired sessions every 5 minutes -crons.interval( - 'cleanup expired sessions', - { minutes: 5 }, - internal.encryption.cleanupExpiredSessions, -) - -export default crons diff --git a/convex/encryption.ts b/convex/encryption.ts deleted file mode 100644 index b89360d..0000000 --- a/convex/encryption.ts +++ /dev/null @@ -1,165 +0,0 @@ -import { v } from 'convex/values' -import { mutation, query } from './_generated/server' - -const SESSION_TTL_MS = 7 * 24 * 60 * 60 * 1000 // 7 days (sliding window) - -// Create or update encryption session -export const createSession = mutation({ - args: { - sessionId: v.string(), - encryptedSessionData: v.string(), - sessionKeyHash: v.string(), - }, - handler: async (ctx, args) => { - const identity = await ctx.auth.getUserIdentity() - if (!identity) throw new Error('Unauthorized') - - // Delete any existing sessions for this user - const existingSessions = await ctx.db - .query('encryptionSessions') - .withIndex('by_user', (q) => q.eq('userId', identity.subject)) - .collect() - - for (const session of existingSessions) { - await ctx.db.delete(session._id) - } - - // Create new session - const now = Date.now() - return await ctx.db.insert('encryptionSessions', { - userId: identity.subject, - sessionId: args.sessionId, - encryptedSessionData: args.encryptedSessionData, - sessionKeyHash: args.sessionKeyHash, - expiresAt: now + SESSION_TTL_MS, - lastAccessedAt: now, - createdAt: now, - }) - }, -}) - -// Get current session -export const getSession = query({ - args: { sessionId: v.string() }, - handler: async (ctx, args) => { - const identity = await ctx.auth.getUserIdentity() - if (!identity) return null - - const session = await ctx.db - .query('encryptionSessions') - .withIndex('by_session_id', (q) => q.eq('sessionId', args.sessionId)) - .first() - - if (!session) return null - - // Check if session belongs to user - if (session.userId !== identity.subject) return null - - // Check if expired (deletion handled by cron job or createSession) - if (Date.now() > session.expiresAt) { - return null - } - - return { - encryptedSessionData: session.encryptedSessionData, - sessionKeyHash: session.sessionKeyHash, - expiresAt: session.expiresAt, - } - }, -}) - -// Extend session TTL (called on activity) -export const extendSession = mutation({ - args: { sessionId: v.string() }, - handler: async (ctx, args) => { - const identity = await ctx.auth.getUserIdentity() - if (!identity) throw new Error('Unauthorized') - - const session = await ctx.db - .query('encryptionSessions') - .withIndex('by_session_id', (q) => q.eq('sessionId', args.sessionId)) - .first() - - if (!session || session.userId !== identity.subject) { - throw new Error('Session not found') - } - - // Check if already expired - if (Date.now() > session.expiresAt) { - await ctx.db.delete(session._id) - throw new Error('Session expired') - } - - const now = Date.now() - await ctx.db.patch(session._id, { - expiresAt: now + SESSION_TTL_MS, - lastAccessedAt: now, - }) - - return { expiresAt: now + SESSION_TTL_MS } - }, -}) - -// Delete session (logout) -export const deleteSession = mutation({ - args: { sessionId: v.string() }, - handler: async (ctx, args) => { - const identity = await ctx.auth.getUserIdentity() - if (!identity) throw new Error('Unauthorized') - - const session = await ctx.db - .query('encryptionSessions') - .withIndex('by_session_id', (q) => q.eq('sessionId', args.sessionId)) - .first() - - if (session && session.userId === identity.subject) { - await ctx.db.delete(session._id) - } - }, -}) - -// Get user's encryption keys (for initial setup) -export const getUserKeys = query({ - handler: async (ctx) => { - const identity = await ctx.auth.getUserIdentity() - if (!identity) return null - - const keys = await ctx.db - .query('userKeys') - .withIndex('by_user', (q) => q.eq('userId', identity.subject)) - .first() - - return keys - }, -}) - -// Store user's PGP keys (on signup) -export const storeUserKeys = mutation({ - args: { - encryptedPrivateKey: v.string(), - publicKey: v.string(), - }, - handler: async (ctx, args) => { - const identity = await ctx.auth.getUserIdentity() - if (!identity) throw new Error('Unauthorized') - - const existing = await ctx.db - .query('userKeys') - .withIndex('by_user', (q) => q.eq('userId', identity.subject)) - .first() - - if (existing) { - throw new Error('Keys already exist for this user') - } - - return await ctx.db.insert('userKeys', { - userId: identity.subject, - encryptedPrivateKey: args.encryptedPrivateKey, - publicKey: args.publicKey, - createdAt: Date.now(), - }) - }, -}) - -// Re-export cleanup function from encryptionSessions -export { cleanupExpiredSessions } from './encryptionSessions' diff --git a/convex/encryptionSessions.ts b/convex/encryptionSessions.ts deleted file mode 100644 index fe886b8..0000000 --- a/convex/encryptionSessions.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { internalMutation } from './_generated/server' - -export const cleanupExpiredSessions = internalMutation({ - handler: async (ctx) => { - const now = Date.now() - const expiredSessions = await ctx.db - .query('encryptionSessions') - .withIndex('by_expiry') - .filter((q) => q.lt(q.field('expiresAt'), now)) - .collect() - - for (const session of expiredSessions) { - await ctx.db.delete(session._id) - } - - console.log(`Cleaned up ${expiredSessions.length} expired sessions`) - }, -}) diff --git a/convex/schema.ts b/convex/schema.ts index 2692adc..bbcab02 100644 --- a/convex/schema.ts +++ b/convex/schema.ts @@ -2,23 +2,16 @@ import { defineSchema, defineTable } from 'convex/server' import { v } from 'convex/values' export default defineSchema({ + /** + * User keys + * This table stores the user's encryption keys. + * It is used to store the *encrypted private key* and the *public key*. + */ userKeys: defineTable({ - userId: v.string(), // Clerk user ID + userId: v.string(), encryptedPrivateKey: v.string(), publicKey: v.string(), + encryptedPassphrase: v.string(), createdAt: v.number(), }).index('by_user', ['userId']), - - encryptionSessions: defineTable({ - userId: v.string(), - sessionId: v.string(), // Random session identifier - encryptedSessionData: v.string(), // Private key encrypted with session key - sessionKeyHash: v.string(), // Hash of session key (for verification) - expiresAt: v.number(), // Auto-delete after 30 minutes - lastAccessedAt: v.number(), // For activity tracking - createdAt: v.number(), - }) - .index('by_user', ['userId']) - .index('by_session_id', ['sessionId']) - .index('by_expiry', ['expiresAt']), // For cleanup }) diff --git a/convex/userKeys.ts b/convex/userKeys.ts new file mode 100644 index 0000000..a67ad9a --- /dev/null +++ b/convex/userKeys.ts @@ -0,0 +1,51 @@ +import { v } from 'convex/values' +import { mutation, query } from './_generated/server' + +/** + * Get the user's encryption keys + */ +export const getUserKeys = query({ + handler: async (ctx) => { + const identity = await ctx.auth.getUserIdentity() + if (!identity) return null + + const keys = await ctx.db + .query('userKeys') + .withIndex('by_user', (q) => q.eq('userId', identity.subject)) + .first() + + return keys + }, +}) + +/** + * Create the user's encryption data (PGP keys + encrypted passphrase) on signup + */ +export const createUserKeys = mutation({ + args: { + encryptedPrivateKey: v.string(), + publicKey: v.string(), + encryptedPassphrase: v.string(), + }, + handler: async (ctx, args) => { + const identity = await ctx.auth.getUserIdentity() + if (!identity) throw new Error('Unauthorized') + + const existing = await ctx.db + .query('userKeys') + .withIndex('by_user', (q) => q.eq('userId', identity.subject)) + .first() + + if (existing) { + throw new Error('Keys already exist for this user') + } + + return await ctx.db.insert('userKeys', { + userId: identity.subject, + encryptedPrivateKey: args.encryptedPrivateKey, + publicKey: args.publicKey, + encryptedPassphrase: args.encryptedPassphrase, + createdAt: Date.now(), + }) + }, +}) diff --git a/messages/en.json b/messages/en.json index 2542f46..c98008b 100644 --- a/messages/en.json +++ b/messages/en.json @@ -1,6 +1,8 @@ { "$schema": "https://inlang.com/schema/inlang-message-format", "navigation": { + "dashboard": "Dashboard", + "encrypt": "Encryption Demo", "logout": "Logout" }, "sign_in": { diff --git a/messages/fr.json b/messages/fr.json index 7e32395..567672b 100644 --- a/messages/fr.json +++ b/messages/fr.json @@ -1,6 +1,8 @@ { "$schema": "https://inlang.com/schema/inlang-message-format", "navigation": { + "dashboard": "Tableau de bord", + "encrypt": "Démo de chiffrement", "logout": "Se déconnecter" }, "sign_in": { diff --git a/package-lock.json b/package-lock.json index 4d0af2b..369b015 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,51 +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", - "@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", + "@scure/bip39": "^2.0.1", + "@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.27.3", - "lucide-react": "^0.561.0", + "convex": "^1.32.0", + "jotai": "^2.18.0", + "jotai-devtools": "^0.13.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", - "jsdom": "^27.0.0", - "prettier": "^3.5.3", - "typescript": "^5.7.2", - "vite": "^7.1.7", - "vitest": "^3.0.5" + "jotai-babel": "^0.1.0", + "jsdom": "^28.1.0", + "prettier": "^3.8.1", + "typescript": "^5.9.3", + "vite": "^7.3.1", + "vitest": "^4.0.18" } }, "node_modules/@acemir/cssom": { @@ -369,7 +374,6 @@ "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -420,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": { @@ -438,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": { @@ -609,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", @@ -1167,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", @@ -1181,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" }, @@ -1257,6 +1307,21 @@ "@floating-ui/utils": "^0.2.10" } }, + "node_modules/@floating-ui/react": { + "version": "0.26.28", + "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.26.28.tgz", + "integrity": "sha512-yORQuuAtVpiRjpMhdc0wJj06b9JFjrYF4qp96j++v2NBpbi6SEGF7donUJ3TMieerQ6qVkAv1tgr7L4r5roTqw==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.1.2", + "@floating-ui/utils": "^0.2.8", + "tabbable": "^6.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, "node_modules/@floating-ui/react-dom": { "version": "2.1.7", "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.7.tgz", @@ -1463,6 +1528,50 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/@mantine/code-highlight": { + "version": "7.17.8", + "resolved": "https://registry.npmjs.org/@mantine/code-highlight/-/code-highlight-7.17.8.tgz", + "integrity": "sha512-KfdLi8MhpoeHiXkLMfuPQz1IDTUjvP2w3hbdx8Oz/hL0o+mbPYfgrU/w/D3ONjVQMoEbPpEL5vSA2eTYCmwVKg==", + "license": "MIT", + "dependencies": { + "clsx": "^2.1.1", + "highlight.js": "^11.10.0" + }, + "peerDependencies": { + "@mantine/core": "7.17.8", + "@mantine/hooks": "7.17.8", + "react": "^18.x || ^19.x", + "react-dom": "^18.x || ^19.x" + } + }, + "node_modules/@mantine/core": { + "version": "7.17.8", + "resolved": "https://registry.npmjs.org/@mantine/core/-/core-7.17.8.tgz", + "integrity": "sha512-42sfdLZSCpsCYmLCjSuntuPcDg3PLbakSmmYfz5Auea8gZYLr+8SS5k647doVu0BRAecqYOytkX2QC5/u/8VHw==", + "license": "MIT", + "dependencies": { + "@floating-ui/react": "^0.26.28", + "clsx": "^2.1.1", + "react-number-format": "^5.4.3", + "react-remove-scroll": "^2.6.2", + "react-textarea-autosize": "8.5.9", + "type-fest": "^4.27.0" + }, + "peerDependencies": { + "@mantine/hooks": "7.17.8", + "react": "^18.x || ^19.x", + "react-dom": "^18.x || ^19.x" + } + }, + "node_modules/@mantine/hooks": { + "version": "7.17.8", + "resolved": "https://registry.npmjs.org/@mantine/hooks/-/hooks-7.17.8.tgz", + "integrity": "sha512-96qygbkTjRhdkzd5HDU8fMziemN/h758/EwrFu7TlWrEP10Vw076u+Ap/sG6OT4RGPZYYoHrTlT+mkCZblWHuw==", + "license": "MIT", + "peerDependencies": { + "react": "^18.x || ^19.x" + } + }, "node_modules/@napi-rs/wasm-runtime": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.1.tgz", @@ -1479,6 +1588,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", @@ -3034,6 +3155,19 @@ "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", "license": "MIT" }, + "node_modules/@redux-devtools/extension": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@redux-devtools/extension/-/extension-3.3.0.tgz", + "integrity": "sha512-X34S/rC8S/M1BIrkYD1mJ5f8vlH0BDqxXrs96cvxSBo4FhMdbhU+GUGsmNYov1xjSyLMHgo8NYrUG8bNX7525g==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.2", + "immutable": "^4.3.4" + }, + "peerDependencies": { + "redux": "^3.1.0 || ^4.0.0 || ^5.0.0" + } + }, "node_modules/@rolldown/binding-android-arm64": { "version": "1.0.0-rc.5", "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.5.tgz", @@ -3573,6 +3707,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", @@ -3664,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", @@ -3969,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", @@ -3999,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" @@ -4013,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" @@ -4032,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" @@ -4064,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": { @@ -4076,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" @@ -4092,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" @@ -4106,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": { @@ -4181,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" @@ -4236,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" @@ -4261,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" @@ -4276,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" }, @@ -4288,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" @@ -4311,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": { @@ -4339,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" }, @@ -4363,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" @@ -4387,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": { @@ -4405,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", @@ -4427,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", @@ -4444,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": { @@ -4454,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", @@ -4486,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", @@ -4497,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", @@ -4514,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" @@ -4547,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" @@ -4588,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" @@ -4622,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", @@ -4684,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" @@ -4706,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" @@ -4722,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", @@ -4854,6 +5007,12 @@ "@babel/types": "^7.28.2" } }, + "node_modules/@types/base16": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/base16/-/base16-1.0.5.tgz", + "integrity": "sha512-OzOWrTluG9cwqidEzC/Q6FAmIPcnZfm8BFRlIx0+UIUqnuAmi5OS88O0RpT3Yz6qdmqObvUhasrbNsCofE4W9A==", + "license": "MIT" + }, "node_modules/@types/chai": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", @@ -4886,16 +5045,29 @@ "license": "MIT", "peer": true }, + "node_modules/@types/lodash": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-RDvF6wTulMPjrNdCoYRC8gNR880JNGT8uB+REUpC2Ns4pRqQJhGz90wh7rgdXDPpCczF3VGktDuFGVnz8zP7HA==", + "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": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "license": "MIT", + "peer": true + }, "node_modules/@types/react": { "version": "19.2.14", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", @@ -5108,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", @@ -5513,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": { @@ -5557,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": { @@ -5600,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" @@ -5659,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", @@ -5804,10 +5928,19 @@ } }, "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "version": "4.0.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/base16": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/base16/-/base16-1.0.0.tgz", + "integrity": "sha512-pNdYkNPiJUnEhnfXV56+sQy8+AaPcG3POZAUnwr4EeqCUZFz4u2PePbo3e5Gj4ziYPCWGUZT9RHisvJKnwFuBQ==", "license": "MIT" }, "node_modules/baseline-browser-mapping": { @@ -5848,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": { @@ -5904,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", @@ -5946,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" } @@ -5975,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", @@ -6072,6 +6179,16 @@ "node": ">=6" } }, + "node_modules/color": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", + "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.3", + "color-string": "^1.6.0" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -6090,9 +6207,32 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, + "license": "MIT" + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", "license": "MIT", - "peer": true + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/color/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "license": "MIT" }, "node_modules/commander": { "version": "11.1.0", @@ -6129,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", @@ -6154,13 +6286,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" @@ -6186,6 +6319,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", @@ -6272,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" @@ -6304,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": { @@ -6400,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", @@ -6629,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, @@ -6642,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", @@ -6802,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", @@ -6937,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", @@ -6986,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", @@ -7261,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": { @@ -7354,6 +7485,15 @@ "node": ">=8" } }, + "node_modules/highlight.js": { + "version": "11.11.1", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz", + "integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/hookable": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/hookable/-/hookable-6.0.1.tgz", @@ -7480,6 +7620,12 @@ "node": ">= 4" } }, + "node_modules/immutable": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz", + "integrity": "sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==", + "license": "MIT" + }, "node_modules/import-fresh": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", @@ -7509,6 +7655,12 @@ "node": ">=0.8.19" } }, + "node_modules/is-arrayish": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz", + "integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==", + "license": "MIT" + }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -7575,6 +7727,12 @@ "license": "ISC", "peer": true }, + "node_modules/javascript-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/javascript-stringify/-/javascript-stringify-2.1.0.tgz", + "integrity": "sha512-JVAfqNPTvNq3sB/VHQJAFxN/sPgKnsKrCwyRt15zwNCdrMMJDdcEOdubuy+DuJYYdm0ox1J4uzEuYKkN+9yhVg==", + "license": "MIT" + }, "node_modules/jiti": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", @@ -7584,6 +7742,106 @@ "jiti": "lib/jiti-cli.mjs" } }, + "node_modules/jotai": { + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/jotai/-/jotai-2.18.0.tgz", + "integrity": "sha512-XI38kGWAvtxAZ+cwHcTgJsd+kJOJGf3OfL4XYaXWZMZ7IIY8e53abpIHvtVn1eAgJ5dlgwlGFnP4psrZ/vZbtA==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0", + "@babel/template": ">=7.0.0", + "@types/react": ">=17.0.0", + "react": ">=17.0.0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@babel/template": { + "optional": true + }, + "@types/react": { + "optional": true + }, + "react": { + "optional": true + } + } + }, + "node_modules/jotai-babel": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/jotai-babel/-/jotai-babel-0.1.0.tgz", + "integrity": "sha512-nNrxxP3c9IQpK4NaZdJBir0rdRcqebJgSc+IHJZNUtL2t7fVuwUG2cUxy9ObZJ6woih13NnSGTmbPuIR0v2Vqg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@babel/core": ">=7.0.0", + "@babel/template": ">=7.0.0", + "jotai": ">=2.0.0" + } + }, + "node_modules/jotai-devtools": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/jotai-devtools/-/jotai-devtools-0.13.0.tgz", + "integrity": "sha512-YI5cXCZA806H+7IEqU3Mw8LG2MeEdoI5eKKLmGFJLCFBI0J/M5u9v714xwWuCw7SM5CjAjRtd801zFOU3kyUTg==", + "license": "MIT", + "dependencies": { + "@mantine/code-highlight": "^7.17.4", + "@mantine/core": "^7.17.4", + "@mantine/hooks": "^7.17.4", + "@redux-devtools/extension": "^3.3.0", + "clsx": "^2.1.1", + "javascript-stringify": "^2.1.0", + "jsondiffpatch": "^0.5.0", + "react-base16-styling": "^0.9.1", + "react-error-boundary": "^5.0.0", + "react-json-tree": "^0.18.0", + "react-resizable-panels": "2.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "jotai": ">=2.14.0", + "react": ">=17.0.0" + } + }, + "node_modules/jotai-devtools/node_modules/@types/react": { + "version": "18.3.28", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz", + "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, + "node_modules/jotai-devtools/node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT", + "peer": true + }, + "node_modules/jotai-devtools/node_modules/react-json-tree": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/react-json-tree/-/react-json-tree-0.18.0.tgz", + "integrity": "sha512-Qe6HKSXrr++n9Y31nkRJ3XvQMATISpqigH1vEKhLwB56+nk5thTP0ITThpjxY6ZG/ubpVq/aEHIcyLP/OPHxeA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.20.6", + "@types/lodash": "^4.14.191", + "react-base16-styling": "^0.9.1" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/js-cookie": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", @@ -7619,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", @@ -7639,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": { @@ -7684,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", @@ -7704,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", @@ -7732,6 +7993,21 @@ "node": ">=6" } }, + "node_modules/jsondiffpatch": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/jsondiffpatch/-/jsondiffpatch-0.7.3.tgz", + "integrity": "sha512-zd4dqFiXSYyant2WgSXAZ9+yYqilNVvragVNkNRn2IFZKgjyULNrKRznqN4Zon0MkLueCg+3QaPVCnDAVP20OQ==", + "license": "MIT", + "dependencies": { + "@dmsnell/diff-match-patch": "^1.1.0" + }, + "bin": { + "jsondiffpatch": "bin/jsondiffpatch.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -8045,6 +8321,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash.curry": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.curry/-/lodash.curry-4.1.1.tgz", + "integrity": "sha512-/u14pXGviLaweY5JI0IUzgzF2J6Ne8INyzAZjImcryjgkZ+ebruBxy2/JaOOkTqScddcYtakjhSaeemV8lR0tA==", + "license": "MIT" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -8053,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", @@ -8070,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" @@ -8105,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": { @@ -8364,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", @@ -8376,6 +8664,15 @@ "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", "license": "MIT" }, + "node_modules/openpgp": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/openpgp/-/openpgp-6.3.0.tgz", + "integrity": "sha512-pLzCU8IgyKXPSO11eeharQkQ4GzOKNWhXq79pQarIRZEMt1/ssyr+MIuWBv1mNoenJLg04gvPx+fi4gcKZ4bag==", + "license": "LGPL-3.0+", + "engines": { + "node": ">= 18.0.0" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -8520,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", @@ -8713,6 +9000,21 @@ "node": ">=0.10.0" } }, + "node_modules/react-base16-styling": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/react-base16-styling/-/react-base16-styling-0.9.1.tgz", + "integrity": "sha512-1s0CY1zRBOQ5M3T61wetEpvQmsYSNtWEcdYzyZNxKa8t7oDvaOn9d21xrGezGAHFWLM7SHcktPuPTrvoqxSfKw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.16.7", + "@types/base16": "^1.0.2", + "@types/lodash": "^4.14.178", + "base16": "^1.0.0", + "color": "^3.2.1", + "csstype": "^3.0.10", + "lodash.curry": "^4.1.1" + } + }, "node_modules/react-dom": { "version": "19.2.4", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", @@ -8725,6 +9027,18 @@ "react": "^19.2.4" } }, + "node_modules/react-error-boundary": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-5.0.0.tgz", + "integrity": "sha512-tnjAxG+IkpLephNcePNA7v6F/QpWLH8He65+DmedchDwg162JZqx4NmbXj0mlAYVVEd81OW7aFhmbsScYfiAFQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "peerDependencies": { + "react": ">=16.13.1" + } + }, "node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", @@ -8732,6 +9046,16 @@ "dev": true, "license": "MIT" }, + "node_modules/react-number-format": { + "version": "5.4.4", + "resolved": "https://registry.npmjs.org/react-number-format/-/react-number-format-5.4.4.tgz", + "integrity": "sha512-wOmoNZoOpvMminhifQYiYSTCLUDOiUbBunrMrMjA+dV52sY+vck1S4UhR6PkgnoCquvvMSeJjErXZ4qSaWCliA==", + "license": "MIT", + "peerDependencies": { + "react": "^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/react-refresh": { "version": "0.18.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz", @@ -8789,6 +9113,16 @@ } } }, + "node_modules/react-resizable-panels": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-2.1.7.tgz", + "integrity": "sha512-JtT6gI+nURzhMYQYsx8DKkx6bSoOGFp7A3CwMrOb8y5jFHFyqwo9m68UhmXRw57fRVJksFn1TSlm3ywEQ9vMgA==", + "license": "MIT", + "peerDependencies": { + "react": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", + "react-dom": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + } + }, "node_modules/react-style-singleton": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", @@ -8811,6 +9145,23 @@ } } }, + "node_modules/react-textarea-autosize": { + "version": "8.5.9", + "resolved": "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-8.5.9.tgz", + "integrity": "sha512-U1DGlIQN5AwgjTyOEnI1oCcMuEr1pv1qOtklB2l4nyMGbHzWrI0eFsYK0zos2YWqAolJyG0IWJaqWmWj5ETh0A==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.20.13", + "use-composed-ref": "^1.3.0", + "use-latest": "^1.2.1" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -8860,6 +9211,13 @@ "node": ">=0.10.0" } }, + "node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", + "license": "MIT", + "peer": true + }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -9077,6 +9435,15 @@ "dev": true, "license": "ISC" }, + "node_modules/simple-swizzle": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz", + "integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, "node_modules/solid-js": { "version": "1.9.11", "resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.9.11.tgz", @@ -9119,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" @@ -9167,26 +9534,6 @@ "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", @@ -9221,10 +9568,16 @@ "dev": true, "license": "MIT" }, + "node_modules/tabbable": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.4.0.tgz", + "integrity": "sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==", + "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", @@ -9270,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", @@ -9292,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": { @@ -9484,6 +9820,18 @@ "node": ">= 0.8.0" } }, + "node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", @@ -9538,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" }, @@ -9672,6 +10020,51 @@ } } }, + "node_modules/use-composed-ref": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/use-composed-ref/-/use-composed-ref-1.4.0.tgz", + "integrity": "sha512-djviaxuOOh7wkj0paeO1Q/4wMZ8Zrnag5H6yBvzN7AKKe8beOaED9SF5/ByLqsku8NP4zQqsvM2u3ew/tJK8/w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-isomorphic-layout-effect": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.2.1.tgz", + "integrity": "sha512-tpZZ+EX0gaghDAiFR37hj5MgY6ZN55kLiPkJsKxBMZ6GZdOSPJXiOzPM984oPYZ5AnehYx5WQp1+ME8I/P/pRA==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-latest": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/use-latest/-/use-latest-1.3.0.tgz", + "integrity": "sha512-mhg3xdm9NaM8q+gLT8KryJPnRFOz1/5XPBhmDEVZK1webPzDjrPk7f/mbpeLqTgB9msytYWANxgALOCJKnLvcQ==", + "license": "MIT", + "dependencies": { + "use-isomorphic-layout-effect": "^1.1.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/use-sidecar": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", @@ -9791,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", @@ -9826,11 +10196,6 @@ }, "peerDependencies": { "vite": "*" - }, - "peerDependenciesMeta": { - "vite": { - "optional": true - } } }, "node_modules/vitefu": { @@ -9853,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": "*" }, @@ -9905,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": { @@ -10014,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 7468610..974bdf6 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", @@ -10,53 +11,63 @@ "lint": "eslint", "format": "prettier --check .", "validate": "prettier --write . && eslint --fix", - "prepare": "husky" + "prepare": "husky", + "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", - "@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", + "@scure/bip39": "^2.0.1", + "@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.27.3", - "lucide-react": "^0.561.0", + "convex": "^1.32.0", + "jotai": "^2.18.0", + "jotai-devtools": "^0.13.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", - "jsdom": "^27.0.0", - "prettier": "^3.5.3", - "typescript": "^5.7.2", - "vite": "^7.1.7", - "vitest": "^3.0.5" + "jotai-babel": "^0.1.0", + "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__/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/__tests__/crypto.test.ts b/src/__tests__/crypto.test.ts new file mode 100644 index 0000000..0b3dcef --- /dev/null +++ b/src/__tests__/crypto.test.ts @@ -0,0 +1,102 @@ +/** + * @vitest-environment node + * OpenPGP has Uint8Array/instanceof issues in jsdom - run in Node + */ +import { describe, expect, it } from 'vitest' +import { + createPassphrase, + decryptPassphrase, + decryptWithPrivateKey, + encryptPassphrase, + encryptWithPublicKey, + generateKeyPair, +} 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 decrypting 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('') + }) + }) + + 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 new file mode 100644 index 0000000..a4c0c55 --- /dev/null +++ b/src/__tests__/useEncryption.test.tsx @@ -0,0 +1,97 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest' +import { renderHook } from '@testing-library/react' +import { useEncryption } from '@/hooks/useEncryption' + +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() +vi.mock('convex/react', () => ({ + useQuery: () => mockUserKeys(), +})) + +describe('useEncryption', () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + describe('encryptData / decryptData', () => { + it('encrypts data when userKeys are available', async () => { + mockUserKeys.mockReturnValue({ + publicKey: 'armored-pub-key', + encryptedPrivateKey: 'armored-priv-key', + }) + mockEncryptWithPublicKey.mockResolvedValue('-----BEGIN PGP MESSAGE-----') + + const { result } = renderHook(() => useEncryption()) + + const encrypted = await result.current.encryptData('Secret message') + expect(encrypted).toContain('-----BEGIN PGP MESSAGE-----') + expect(mockEncryptWithPublicKey).toHaveBeenCalledWith( + 'Secret message', + 'armored-pub-key', + ) + }) + + it('decrypts data when userKeys and passphrase are available', async () => { + mockUserKeys.mockReturnValue({ + publicKey: 'armored-pub-key', + encryptedPrivateKey: 'armored-priv-key', + }) + mockDecryptWithPrivateKey.mockResolvedValue('decrypted plaintext') + + const { result } = renderHook(() => useEncryption()) + + const decrypted = await result.current.decryptData( + 'encrypted-data', + 'passphrase', + ) + expect(decrypted).toBe('decrypted plaintext') + expect(mockDecryptWithPrivateKey).toHaveBeenCalledWith( + 'encrypted-data', + 'armored-priv-key', + 'passphrase', + ) + }) + + 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..dc2f972 --- /dev/null +++ b/src/__tests__/useSignIn.test.tsx @@ -0,0 +1,205 @@ +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 mockAttemptSecondFactor = vi.fn() +const mockSetActive = vi.fn() +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: mockPrepareSecondFactor, + attemptSecondFactor: mockAttemptSecondFactor, + }, + 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() + }) + + 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('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 () => { + await 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 () => { + await result.current.handleSignIn({ + preventDefault: vi.fn(), + } as unknown as React.SubmitEvent) + }) + + expect(result.current.error).toBe('Invalid credentials') + 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 () => { + await 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()) + + await act(async () => { + await result.current.handleSignIn({ + preventDefault: vi.fn(), + } as unknown as React.SubmitEvent) + }) + + 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 () => { + await 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 () => { + await result.current.handleMfaVerify({ + preventDefault: vi.fn(), + } as unknown as React.SubmitEvent) + }) + + expect(result.current.error).toBe('Invalid code') + expect(mockRouter).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..d5220f0 --- /dev/null +++ b/src/__tests__/useSignUp.test.tsx @@ -0,0 +1,172 @@ +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.hoisted(() => ({ + 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('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'), + generateKeyPair: mockGenerateKeyPair, +})) + +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 () => { + await 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 () => { + await 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 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 () => { + await 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')) + + const { result } = renderHook(() => useSignUp()) + + act(() => { + result.current.setEmail('taken@example.com') + result.current.setPassword('pass') + }) + + await act(async () => { + await 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/src/components/Forms/SignIn/index.ts b/src/components/Forms/SignIn/index.ts new file mode 100644 index 0000000..756a7e3 --- /dev/null +++ b/src/components/Forms/SignIn/index.ts @@ -0,0 +1,2 @@ +export * from './SignInCredsForm' +export * from './SignInMfaForm' diff --git a/src/components/Forms/SignIn/index.tsx b/src/components/Forms/SignIn/index.tsx deleted file mode 100644 index fbf76cf..0000000 --- a/src/components/Forms/SignIn/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export * from './SignInForm' diff --git a/src/components/Forms/SignUp/index.ts b/src/components/Forms/SignUp/index.ts index 0d400e8..843ba6d 100644 --- a/src/components/Forms/SignUp/index.ts +++ b/src/components/Forms/SignUp/index.ts @@ -1 +1,2 @@ -export * from './SignUpForm' +export * from './SignUpCredsForm' +export * from './SignUpVerificationForm' diff --git a/src/components/Layouts/LayoutAside.tsx b/src/components/Layouts/LayoutAside.tsx index b7d8897..f26f175 100644 --- a/src/components/Layouts/LayoutAside.tsx +++ b/src/components/Layouts/LayoutAside.tsx @@ -1,9 +1,9 @@ import { UserNavigation } from './Navigation' +import { MainNavigation } from './Navigation/MainNavigation' import { Sidebar, SidebarContent, SidebarFooter, - SidebarGroup, SidebarHeader, } from '@/components/ui/sidebar' @@ -12,8 +12,7 @@ export const LayoutAside = () => { - - + diff --git a/src/components/Layouts/Navigation/MainNavigation.tsx b/src/components/Layouts/Navigation/MainNavigation.tsx new file mode 100644 index 0000000..8612b60 --- /dev/null +++ b/src/components/Layouts/Navigation/MainNavigation.tsx @@ -0,0 +1,35 @@ +import { LayoutDashboardIcon, MessageSquareLockIcon } from 'lucide-react' +import { useNavigate } from '@tanstack/react-router' +import { NavigationLink } from './NavigationLink' +import { + SidebarGroup, + SidebarGroupContent, + SidebarMenu, +} from '@/components/ui/sidebar' +import { m } from '@/paraglide/messages' +import { RoutesPath } from '@/types/routes' + +export const MainNavigation = () => { + const navigate = useNavigate() + return ( + + + + navigate({ to: RoutesPath.HOME.toString() })} + icon={} + label={m['navigation.dashboard']()} + /> + + navigate({ to: RoutesPath.DEMO_ENCRYPT.toString() })} + icon={} + label={m['navigation.encrypt']()} + /> + + + + ) +} diff --git a/src/components/Layouts/Navigation/NavigationLink.tsx b/src/components/Layouts/Navigation/NavigationLink.tsx new file mode 100644 index 0000000..9ff58f8 --- /dev/null +++ b/src/components/Layouts/Navigation/NavigationLink.tsx @@ -0,0 +1,24 @@ +import { SidebarMenuButton, SidebarMenuItem } from '@/components/ui/sidebar' + +type NavigationLinkProps = { + icon?: React.ReactNode + label: string + onClick?: () => void + tooltip: string +} + +export const NavigationLink = ({ + tooltip, + onClick, + icon, + label, +}: NavigationLinkProps) => { + return ( + + + {icon} + {label} + + + ) +} diff --git a/src/components/ui/textarea.tsx b/src/components/ui/textarea.tsx new file mode 100644 index 0000000..3809775 --- /dev/null +++ b/src/components/ui/textarea.tsx @@ -0,0 +1,18 @@ +import * as React from 'react' + +import { cn } from '@/lib/utils' + +function Textarea({ className, ...props }: React.ComponentProps<'textarea'>) { + return ( +