diff --git a/apps/web/package.json b/apps/web/package.json index ad59d31..9306797 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -52,6 +52,9 @@ "@capakraken/eslint-config": "workspace:*", "@capakraken/tsconfig": "workspace:*", "@playwright/test": "^1.49.1", + "@testing-library/jest-dom": "^6.9.1", + "@testing-library/react": "^16.3.2", + "@testing-library/user-event": "^14.6.1", "@types/dompurify": "^3.2.0", "@types/node": "^22.10.2", "@types/react": "^19.0.6", @@ -61,6 +64,7 @@ "@vitest/coverage-v8": "^2.1.9", "autoprefixer": "^10.4.20", "eslint": "^10.2.0", + "jsdom": "^29.0.2", "postcss": "^8.4.49", "tailwindcss": "^3.4.17", "typescript": "^5.6.3", diff --git a/apps/web/src/components/projects/ProjectWizard.tsx b/apps/web/src/components/projects/ProjectWizard.tsx index 497fb17..be99eb2 100644 --- a/apps/web/src/components/projects/ProjectWizard.tsx +++ b/apps/web/src/components/projects/ProjectWizard.tsx @@ -1,123 +1,35 @@ "use client"; import { createPortal } from "react-dom"; -import { useState, useCallback, useEffect, useMemo, useRef } from "react"; +import { useState, useEffect, useMemo, useRef } from "react"; import { clsx } from "clsx"; -import type { - StaffingRequirement, - BlueprintFieldDefinition, - OrderType, - AllocationType, -} from "@capakraken/shared"; -import { - BlueprintTarget, - FieldType, - ProjectStatus, - AllocationStatus, - RolePresetsSchema, -} from "@capakraken/shared"; +import type { StaffingRequirement, BlueprintFieldDefinition } from "@capakraken/shared"; +import { BlueprintTarget, FieldType, RolePresetsSchema } from "@capakraken/shared"; import { trpc } from "~/lib/trpc/client.js"; import { uuid } from "~/lib/uuid.js"; import { DateInput } from "~/components/ui/DateInput.js"; import { SkillTagInput } from "~/components/ui/SkillTagInput.js"; import { usePermissions } from "~/hooks/usePermissions.js"; import { InfoTooltip } from "~/components/ui/InfoTooltip.js"; -import { formatCents, toDateInputValue } from "~/lib/format.js"; +import { formatCents } from "~/lib/format.js"; import { ConfettiBurst } from "~/components/ui/ConfettiBurst.js"; import { SuccessToast } from "~/components/ui/SuccessToast.js"; import { useAnchoredOverlay } from "~/hooks/useAnchoredOverlay.js"; - -// ─── Constants ──────────────────────────────────────────────────────────────── - -const ORDER_TYPE_OPTIONS = [ - { value: "BD", label: "BD" }, - { value: "CHARGEABLE", label: "Chargeable" }, - { value: "INTERNAL", label: "Internal" }, - { value: "OVERHEAD", label: "Overhead" }, -] as const; - -const ALLOCATION_TYPE_OPTIONS = [ - { value: "INT", label: "INT" }, - { value: "EXT", label: "EXT" }, -] as const; - -const BTN_PRIMARY = - "px-5 py-2 bg-brand-600 text-white rounded-lg hover:bg-brand-700 text-sm font-medium disabled:opacity-50 transition-colors"; - -const BTN_SECONDARY = - "px-5 py-2 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 text-sm font-medium transition-colors"; - -// ─── Types ──────────────────────────────────────────────────────────────────── - -interface Assignment { - requirementId: string; - resourceId: string; - resourceName: string; - role: string; -} - -interface WizardState { - blueprintId: string | null; - shortCode: string; - name: string; - orderType: string; - allocationType: string; - startDate: string; - endDate: string; - budgetEur: string; - winProbability: number; - responsiblePerson: string; - staffingReqs: StaffingRequirement[]; - assignments: Assignment[]; - saveAsDraft: boolean; - dynamicFields: Record; - blueprintName: string | null; - blueprintFieldDefs: BlueprintFieldDefinition[]; -} - -function makeDefaultState(): WizardState { - const today = toDateInputValue(new Date()); - return { - blueprintId: null, - shortCode: "", - name: "", - orderType: "CHARGEABLE", - allocationType: "INT", - startDate: today, - endDate: today, - budgetEur: "", - winProbability: 100, - responsiblePerson: "", - staffingReqs: [], - assignments: [], - saveAsDraft: true, - dynamicFields: {}, - blueprintName: null, - blueprintFieldDefs: [], - }; -} - -function makeReq(): StaffingRequirement { - return { - id: uuid(), - role: "", - requiredSkills: [], - preferredSkills: [], - hoursPerDay: 8, - headcount: 1, - }; -} +import { + ORDER_TYPE_OPTIONS, + ALLOCATION_TYPE_OPTIONS, + BTN_PRIMARY, + BTN_SECONDARY, + STEPS, + makeReq, + type Assignment, + type WizardState, + type SuggestionItem, +} from "./project-wizard/types.js"; +import { useProjectWizardForm } from "./project-wizard/useProjectWizardForm.js"; // ─── Step indicators ───────────────────────────────────────────────────────── -const STEPS = [ - "Blueprint & Identity", - "Timeline & Budget", - "Staffing Demand", - "Suggestions", - "Review & Create", -]; - function StepBar({ current }: { current: number }) { return (
@@ -936,18 +848,6 @@ function Step3({ state, onChange }: Step3Props) { // ─── Step 4: Suggestions ───────────────────────────────────────────────────── -// Matches StaffingSuggestion from @capakraken/shared (returned by staffing.getSuggestions) -type SuggestionItem = { - resourceId: string; - resourceName: string; - eid: string; - score: number; - currentUtilization: number; - availabilityConflicts: string[]; - estimatedDailyCostCents: number; - valueScore?: number; -}; - interface ReqSuggestionsProps { req: StaffingRequirement; startDate: string; @@ -1391,191 +1291,21 @@ interface ProjectWizardProps { } export function ProjectWizard({ open, onClose, onSuccess }: ProjectWizardProps) { - const utils = trpc.useUtils(); - const [step, setStep] = useState(0); - const [state, setState] = useState(makeDefaultState); - const [isSubmitting, setIsSubmitting] = useState(false); - const [submitError, setSubmitError] = useState(null); - const [submitWarnings, setSubmitWarnings] = useState([]); - const [showConfetti, setShowConfetti] = useState(false); - const [showSuccessToast, setShowSuccessToast] = useState(false); - - const createProject = trpc.project.create.useMutation(); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const createAssignment = (trpc.allocation.createAssignment.useMutation as any)() as { - mutateAsync: (input: unknown) => Promise; - }; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const createDemandRequirement = ( - trpc.allocation.createDemandRequirement.useMutation as any - )() as { - mutateAsync: (input: unknown) => Promise; - }; - - const patch = useCallback((p: Partial) => { - setState((prev) => ({ ...prev, ...p })); - }, []); - - function reset() { - setStep(0); - setState(makeDefaultState()); - setIsSubmitting(false); - setSubmitError(null); - setSubmitWarnings([]); - } - - function handleClose() { - reset(); - onClose(); - } - - function canGoNext(): boolean { - if (step === 0) { - // Required blueprint fields must be filled - const missingRequired = state.blueprintFieldDefs.some((f) => { - if (!f.required) return false; - const val = state.dynamicFields[f.key]; - return ( - val === undefined || - val === null || - val === "" || - (Array.isArray(val) && val.length === 0) - ); - }); - return ( - state.shortCode.trim().length > 0 && - /^[A-Z0-9_-]+$/.test(state.shortCode) && - state.name.trim().length > 0 && - !missingRequired - ); - } - if (step === 1) { - return ( - !!state.startDate && - !!state.endDate && - state.endDate >= state.startDate && - (state.budgetEur === "" || parseFloat(state.budgetEur) >= 0) - ); - } - if (step === 2) { - // Allow advancing with zero requirements (no mandatory staffing), but - // any requirement that exists must be valid: must have a role and positive hours/headcount. - return state.staffingReqs.every( - (r) => - (r.roleId != null || r.role.trim().length > 0) && r.hoursPerDay > 0 && r.headcount >= 1, - ); - } - return true; - } - - async function handleSubmit() { - setIsSubmitting(true); - setSubmitError(null); - setSubmitWarnings([]); - - try { - const project = await createProject.mutateAsync({ - shortCode: state.shortCode.trim(), - name: state.name.trim(), - orderType: state.orderType as unknown as OrderType, - allocationType: state.allocationType as unknown as AllocationType, - winProbability: state.winProbability, - budgetCents: state.budgetEur ? Math.round(parseFloat(state.budgetEur) * 100) : 0, - startDate: new Date(state.startDate), - endDate: new Date(state.endDate), - staffingReqs: state.staffingReqs, - status: state.saveAsDraft ? ProjectStatus.DRAFT : ProjectStatus.ACTIVE, - responsiblePerson: state.responsiblePerson.trim(), - blueprintId: state.blueprintId ?? undefined, - dynamicFields: state.dynamicFields, - }); - - const warnings: string[] = []; - - // Create draft assignments for assigned resources - for (const assignment of state.assignments) { - try { - const req = state.staffingReqs.find((r) => r.id === assignment.requirementId); - const hoursPerDay = req?.hoursPerDay ?? 8; - const percentage = Math.min(100, Math.round((hoursPerDay / 8) * 100)); - await createAssignment.mutateAsync({ - projectId: project.id, - resourceId: assignment.resourceId, - startDate: new Date(state.startDate), - endDate: new Date(state.endDate), - hoursPerDay, - percentage, - role: assignment.role, - roleId: req?.roleId, - status: AllocationStatus.PROPOSED, - metadata: {}, - }); - } catch (err) { - const msg = err instanceof Error ? err.message : String(err); - warnings.push(`Assignment for "${assignment.resourceName}" failed: ${msg}`); - } - } - - // Create open demand for unassigned slots - for (const req of state.staffingReqs) { - const assignedCount = state.assignments.filter((a) => a.requirementId === req.id).length; - const unassigned = req.headcount - assignedCount; - if (unassigned <= 0) continue; - try { - await createDemandRequirement.mutateAsync({ - projectId: project.id, - startDate: new Date(state.startDate), - endDate: new Date(state.endDate), - hoursPerDay: req.hoursPerDay, - percentage: Math.min(100, Math.round((req.hoursPerDay / 8) * 100)), - role: req.role || undefined, - roleId: req.roleId, - headcount: unassigned, - status: AllocationStatus.PROPOSED, - metadata: {}, - }); - } catch (err) { - const msg = err instanceof Error ? err.message : String(err); - warnings.push( - `Demand for "${req.role || "Unnamed Role"}" (${unassigned} seat${unassigned > 1 ? "s" : ""}) failed: ${msg}`, - ); - } - } - - if (warnings.length > 0) setSubmitWarnings(warnings); - - await utils.project.listWithCosts.invalidate(); - await utils.timeline.getEntries.invalidate(); - await utils.timeline.getEntriesView.invalidate(); - setShowConfetti(true); - setShowSuccessToast(true); - setTimeout(() => { - setShowConfetti(false); - onSuccess?.(project.shortCode, project.name); - handleClose(); - }, 1200); - } catch (err) { - let errorMessage = "Failed to create project"; - if (err instanceof Error) { - try { - const parsed: unknown = JSON.parse(err.message); - if (Array.isArray(parsed) && parsed.length > 0) { - errorMessage = (parsed as { message?: string }[]) - .map((e) => e.message) - .filter(Boolean) - .join("; "); - } else { - errorMessage = err.message; - } - } catch { - errorMessage = err.message; - } - } - setSubmitError(errorMessage); - } finally { - setIsSubmitting(false); - } - } + const { + step, + setStep, + state, + patch, + canGoNext, + isSubmitting, + submitError, + submitWarnings, + showConfetti, + showSuccessToast, + setShowSuccessToast, + handleSubmit, + handleClose, + } = useProjectWizardForm({ onClose, onSuccess }); if (!open) return null; diff --git a/apps/web/src/components/projects/project-wizard/types.ts b/apps/web/src/components/projects/project-wizard/types.ts new file mode 100644 index 0000000..682adc2 --- /dev/null +++ b/apps/web/src/components/projects/project-wizard/types.ts @@ -0,0 +1,105 @@ +import type { StaffingRequirement, BlueprintFieldDefinition } from "@capakraken/shared"; +import { toDateInputValue } from "~/lib/format.js"; +import { uuid } from "~/lib/uuid.js"; + +// ─── Constants ──────────────────────────────────────────────────────────────── + +export const ORDER_TYPE_OPTIONS = [ + { value: "BD", label: "BD" }, + { value: "CHARGEABLE", label: "Chargeable" }, + { value: "INTERNAL", label: "Internal" }, + { value: "OVERHEAD", label: "Overhead" }, +] as const; + +export const ALLOCATION_TYPE_OPTIONS = [ + { value: "INT", label: "INT" }, + { value: "EXT", label: "EXT" }, +] as const; + +export const BTN_PRIMARY = + "px-5 py-2 bg-brand-600 text-white rounded-lg hover:bg-brand-700 text-sm font-medium disabled:opacity-50 transition-colors"; + +export const BTN_SECONDARY = + "px-5 py-2 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 text-sm font-medium transition-colors"; + +export const STEPS = [ + "Blueprint & Identity", + "Timeline & Budget", + "Staffing Demand", + "Suggestions", + "Review & Create", +]; + +// ─── Types ──────────────────────────────────────────────────────────────────── + +export interface Assignment { + requirementId: string; + resourceId: string; + resourceName: string; + role: string; +} + +export interface WizardState { + blueprintId: string | null; + shortCode: string; + name: string; + orderType: string; + allocationType: string; + startDate: string; + endDate: string; + budgetEur: string; + winProbability: number; + responsiblePerson: string; + staffingReqs: StaffingRequirement[]; + assignments: Assignment[]; + saveAsDraft: boolean; + dynamicFields: Record; + blueprintName: string | null; + blueprintFieldDefs: BlueprintFieldDefinition[]; +} + +export type SuggestionItem = { + resourceId: string; + resourceName: string; + eid: string; + score: number; + currentUtilization: number; + availabilityConflicts: string[]; + estimatedDailyCostCents: number; + valueScore?: number; +}; + +// ─── Factory functions ──────────────────────────────────────────────────────── + +export function makeDefaultState(): WizardState { + const today = toDateInputValue(new Date()); + return { + blueprintId: null, + shortCode: "", + name: "", + orderType: "CHARGEABLE", + allocationType: "INT", + startDate: today, + endDate: today, + budgetEur: "", + winProbability: 100, + responsiblePerson: "", + staffingReqs: [], + assignments: [], + saveAsDraft: true, + dynamicFields: {}, + blueprintName: null, + blueprintFieldDefs: [], + }; +} + +export function makeReq(): StaffingRequirement { + return { + id: uuid(), + role: "", + requiredSkills: [], + preferredSkills: [], + hoursPerDay: 8, + headcount: 1, + }; +} diff --git a/apps/web/src/components/projects/project-wizard/useProjectWizardForm.test.ts b/apps/web/src/components/projects/project-wizard/useProjectWizardForm.test.ts new file mode 100644 index 0000000..58b7319 --- /dev/null +++ b/apps/web/src/components/projects/project-wizard/useProjectWizardForm.test.ts @@ -0,0 +1,239 @@ +import { describe, expect, it } from "vitest"; +import { canGoNext } from "./useProjectWizardForm.js"; +import { makeDefaultState, makeReq } from "./types.js"; +import type { WizardState } from "./types.js"; + +function stateWith(overrides: Partial): WizardState { + return { ...makeDefaultState(), ...overrides }; +} + +describe("canGoNext", () => { + // ─── Step 0: Blueprint & Identity ──────────────────────────────────────────── + + describe("step 0", () => { + it("returns true when shortCode and name are valid", () => { + const state = stateWith({ shortCode: "PRJ-01", name: "My Project" }); + expect(canGoNext(0, state)).toBe(true); + }); + + it("returns false when shortCode is empty", () => { + const state = stateWith({ shortCode: "", name: "My Project" }); + expect(canGoNext(0, state)).toBe(false); + }); + + it("returns false when shortCode contains lowercase letters", () => { + const state = stateWith({ shortCode: "prj01", name: "My Project" }); + expect(canGoNext(0, state)).toBe(false); + }); + + it("returns false when name is empty", () => { + const state = stateWith({ shortCode: "PRJ01", name: "" }); + expect(canGoNext(0, state)).toBe(false); + }); + + it("returns false when name is only whitespace", () => { + const state = stateWith({ shortCode: "PRJ01", name: " " }); + expect(canGoNext(0, state)).toBe(false); + }); + + it("returns false when a required blueprint field is missing", () => { + const state = stateWith({ + shortCode: "PRJ01", + name: "Test", + blueprintFieldDefs: [ + { key: "field1", type: "TEXT" as never, label: "Field 1", required: true }, + ], + dynamicFields: {}, + }); + expect(canGoNext(0, state)).toBe(false); + }); + + it("returns true when required blueprint field is filled", () => { + const state = stateWith({ + shortCode: "PRJ01", + name: "Test", + blueprintFieldDefs: [ + { key: "field1", type: "TEXT" as never, label: "Field 1", required: true }, + ], + dynamicFields: { field1: "value" }, + }); + expect(canGoNext(0, state)).toBe(true); + }); + + it("returns false when required field is an empty array", () => { + const state = stateWith({ + shortCode: "PRJ01", + name: "Test", + blueprintFieldDefs: [ + { key: "tags", type: "TAG_LIST" as never, label: "Tags", required: true }, + ], + dynamicFields: { tags: [] }, + }); + expect(canGoNext(0, state)).toBe(false); + }); + + it("ignores non-required fields", () => { + const state = stateWith({ + shortCode: "PRJ01", + name: "Test", + blueprintFieldDefs: [ + { key: "optional", type: "TEXT" as never, label: "Optional", required: false }, + ], + dynamicFields: {}, + }); + expect(canGoNext(0, state)).toBe(true); + }); + }); + + // ─── Step 1: Timeline & Budget ───────────────────────────────────────────── + + describe("step 1", () => { + it("returns true with valid dates and no budget", () => { + const state = stateWith({ + startDate: "2026-01-01", + endDate: "2026-12-31", + budgetEur: "", + }); + expect(canGoNext(1, state)).toBe(true); + }); + + it("returns true with valid dates and positive budget", () => { + const state = stateWith({ + startDate: "2026-01-01", + endDate: "2026-12-31", + budgetEur: "50000", + }); + expect(canGoNext(1, state)).toBe(true); + }); + + it("returns false when endDate is before startDate", () => { + const state = stateWith({ + startDate: "2026-12-31", + endDate: "2026-01-01", + }); + expect(canGoNext(1, state)).toBe(false); + }); + + it("returns false when startDate is empty", () => { + const state = stateWith({ startDate: "", endDate: "2026-12-31" }); + expect(canGoNext(1, state)).toBe(false); + }); + + it("returns false when budget is negative", () => { + const state = stateWith({ + startDate: "2026-01-01", + endDate: "2026-12-31", + budgetEur: "-100", + }); + expect(canGoNext(1, state)).toBe(false); + }); + + it("returns true when budget is zero", () => { + const state = stateWith({ + startDate: "2026-01-01", + endDate: "2026-12-31", + budgetEur: "0", + }); + expect(canGoNext(1, state)).toBe(true); + }); + }); + + // ─── Step 2: Staffing Demand ───────────────────────────────────────────────── + + describe("step 2", () => { + it("returns true with no staffing requirements", () => { + const state = stateWith({ staffingReqs: [] }); + expect(canGoNext(2, state)).toBe(true); + }); + + it("returns true with a valid requirement", () => { + const req = { ...makeReq(), role: "Developer", hoursPerDay: 8, headcount: 1 }; + const state = stateWith({ staffingReqs: [req] }); + expect(canGoNext(2, state)).toBe(true); + }); + + it("returns false when a requirement has empty role and no roleId", () => { + const req = { ...makeReq(), role: "", roleId: undefined, hoursPerDay: 8, headcount: 1 }; + const state = stateWith({ staffingReqs: [req] }); + expect(canGoNext(2, state)).toBe(false); + }); + + it("returns true when a requirement has roleId but empty role string", () => { + const req = { ...makeReq(), role: "", roleId: "role_1", hoursPerDay: 8, headcount: 1 }; + const state = stateWith({ staffingReqs: [req] }); + expect(canGoNext(2, state)).toBe(true); + }); + + it("returns false when hoursPerDay is zero", () => { + const req = { ...makeReq(), role: "Dev", hoursPerDay: 0, headcount: 1 }; + const state = stateWith({ staffingReqs: [req] }); + expect(canGoNext(2, state)).toBe(false); + }); + + it("returns false when headcount is less than 1", () => { + const req = { ...makeReq(), role: "Dev", hoursPerDay: 8, headcount: 0 }; + const state = stateWith({ staffingReqs: [req] }); + expect(canGoNext(2, state)).toBe(false); + }); + }); + + // ─── Steps 3+ ──────────────────────────────────────────────────────────────── + + describe("steps 3 and 4", () => { + it("always returns true for step 3", () => { + expect(canGoNext(3, makeDefaultState())).toBe(true); + }); + + it("always returns true for step 4", () => { + expect(canGoNext(4, makeDefaultState())).toBe(true); + }); + }); +}); + +describe("makeDefaultState", () => { + it("returns a state with empty shortCode and name", () => { + const state = makeDefaultState(); + expect(state.shortCode).toBe(""); + expect(state.name).toBe(""); + }); + + it("defaults to CHARGEABLE order type", () => { + expect(makeDefaultState().orderType).toBe("CHARGEABLE"); + }); + + it("defaults to 100% win probability", () => { + expect(makeDefaultState().winProbability).toBe(100); + }); + + it("defaults to saveAsDraft true", () => { + expect(makeDefaultState().saveAsDraft).toBe(true); + }); + + it("sets startDate and endDate to today", () => { + const state = makeDefaultState(); + expect(state.startDate).toMatch(/^\d{4}-\d{2}-\d{2}$/); + expect(state.endDate).toBe(state.startDate); + }); +}); + +describe("makeReq", () => { + it("creates a requirement with a unique id", () => { + const a = makeReq(); + const b = makeReq(); + expect(a.id).not.toBe(b.id); + }); + + it("defaults to 8 hours per day", () => { + expect(makeReq().hoursPerDay).toBe(8); + }); + + it("defaults to headcount 1", () => { + expect(makeReq().headcount).toBe(1); + }); + + it("starts with empty skills", () => { + const req = makeReq(); + expect(req.requiredSkills).toEqual([]); + expect(req.preferredSkills).toEqual([]); + }); +}); diff --git a/apps/web/src/components/projects/project-wizard/useProjectWizardForm.ts b/apps/web/src/components/projects/project-wizard/useProjectWizardForm.ts new file mode 100644 index 0000000..f21ad8a --- /dev/null +++ b/apps/web/src/components/projects/project-wizard/useProjectWizardForm.ts @@ -0,0 +1,215 @@ +import { useState, useCallback } from "react"; +import type { OrderType, AllocationType } from "@capakraken/shared"; +import { ProjectStatus, AllocationStatus } from "@capakraken/shared"; +import { trpc } from "~/lib/trpc/client.js"; +import { makeDefaultState, type WizardState } from "./types.js"; + +// ─── Standalone pure validation function ───────────────────────────────────── + +export function canGoNext(step: number, state: WizardState): boolean { + if (step === 0) { + const missingRequired = state.blueprintFieldDefs.some((f) => { + if (!f.required) return false; + const val = state.dynamicFields[f.key]; + return ( + val === undefined || val === null || val === "" || (Array.isArray(val) && val.length === 0) + ); + }); + return ( + state.shortCode.trim().length > 0 && + /^[A-Z0-9_-]+$/.test(state.shortCode) && + state.name.trim().length > 0 && + !missingRequired + ); + } + if (step === 1) { + return ( + !!state.startDate && + !!state.endDate && + state.endDate >= state.startDate && + (state.budgetEur === "" || parseFloat(state.budgetEur) >= 0) + ); + } + if (step === 2) { + return state.staffingReqs.every( + (r) => + (r.roleId != null || r.role.trim().length > 0) && r.hoursPerDay > 0 && r.headcount >= 1, + ); + } + return true; +} + +// ─── Hook options ───────────────────────────────────────────────────────────── + +export interface UseProjectWizardFormOptions { + onClose: () => void; + onSuccess?: ((shortCode: string, name: string) => void) | undefined; +} + +// ─── Hook ───────────────────────────────────────────────────────────────────── + +export function useProjectWizardForm({ onClose, onSuccess }: UseProjectWizardFormOptions) { + const utils = trpc.useUtils(); + const [step, setStep] = useState(0); + const [state, setState] = useState(makeDefaultState); + const [isSubmitting, setIsSubmitting] = useState(false); + const [submitError, setSubmitError] = useState(null); + const [submitWarnings, setSubmitWarnings] = useState([]); + const [showConfetti, setShowConfetti] = useState(false); + const [showSuccessToast, setShowSuccessToast] = useState(false); + + const createProject = trpc.project.create.useMutation(); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const createAssignment = (trpc.allocation.createAssignment.useMutation as any)() as { + mutateAsync: (input: unknown) => Promise; + }; + + const createDemandRequirement = ( + trpc.allocation.createDemandRequirement.useMutation as any + )() as { + mutateAsync: (input: unknown) => Promise; + }; + + const patch = useCallback((p: Partial) => { + setState((prev) => ({ ...prev, ...p })); + }, []); + + function reset() { + setStep(0); + setState(makeDefaultState()); + setIsSubmitting(false); + setSubmitError(null); + setSubmitWarnings([]); + } + + function handleClose() { + reset(); + onClose(); + } + + async function handleSubmit() { + setIsSubmitting(true); + setSubmitError(null); + setSubmitWarnings([]); + + try { + const project = await createProject.mutateAsync({ + shortCode: state.shortCode.trim(), + name: state.name.trim(), + orderType: state.orderType as unknown as OrderType, + allocationType: state.allocationType as unknown as AllocationType, + winProbability: state.winProbability, + budgetCents: state.budgetEur ? Math.round(parseFloat(state.budgetEur) * 100) : 0, + startDate: new Date(state.startDate), + endDate: new Date(state.endDate), + staffingReqs: state.staffingReqs, + status: state.saveAsDraft ? ProjectStatus.DRAFT : ProjectStatus.ACTIVE, + responsiblePerson: state.responsiblePerson.trim(), + blueprintId: state.blueprintId ?? undefined, + dynamicFields: state.dynamicFields, + }); + + const warnings: string[] = []; + + // Create draft assignments for assigned resources + for (const assignment of state.assignments) { + try { + const req = state.staffingReqs.find((r) => r.id === assignment.requirementId); + const hoursPerDay = req?.hoursPerDay ?? 8; + const percentage = Math.min(100, Math.round((hoursPerDay / 8) * 100)); + await createAssignment.mutateAsync({ + projectId: project.id, + resourceId: assignment.resourceId, + startDate: new Date(state.startDate), + endDate: new Date(state.endDate), + hoursPerDay, + percentage, + role: assignment.role, + roleId: req?.roleId, + status: AllocationStatus.PROPOSED, + metadata: {}, + }); + } catch (err) { + const msg = err instanceof Error ? err.message : String(err); + warnings.push(`Assignment for "${assignment.resourceName}" failed: ${msg}`); + } + } + + // Create open demand for unassigned slots + for (const req of state.staffingReqs) { + const assignedCount = state.assignments.filter((a) => a.requirementId === req.id).length; + const unassigned = req.headcount - assignedCount; + if (unassigned <= 0) continue; + try { + await createDemandRequirement.mutateAsync({ + projectId: project.id, + startDate: new Date(state.startDate), + endDate: new Date(state.endDate), + hoursPerDay: req.hoursPerDay, + percentage: Math.min(100, Math.round((req.hoursPerDay / 8) * 100)), + role: req.role || undefined, + roleId: req.roleId, + headcount: unassigned, + status: AllocationStatus.PROPOSED, + metadata: {}, + }); + } catch (err) { + const msg = err instanceof Error ? err.message : String(err); + warnings.push( + `Demand for "${req.role || "Unnamed Role"}" (${unassigned} seat${unassigned > 1 ? "s" : ""}) failed: ${msg}`, + ); + } + } + + if (warnings.length > 0) setSubmitWarnings(warnings); + + await utils.project.listWithCosts.invalidate(); + await utils.timeline.getEntries.invalidate(); + await utils.timeline.getEntriesView.invalidate(); + setShowConfetti(true); + setShowSuccessToast(true); + setTimeout(() => { + setShowConfetti(false); + onSuccess?.(project.shortCode, project.name); + handleClose(); + }, 1200); + } catch (err) { + let errorMessage = "Failed to create project"; + if (err instanceof Error) { + try { + const parsed: unknown = JSON.parse(err.message); + if (Array.isArray(parsed) && parsed.length > 0) { + errorMessage = (parsed as { message?: string }[]) + .map((e) => e.message) + .filter(Boolean) + .join("; "); + } else { + errorMessage = err.message; + } + } catch { + errorMessage = err.message; + } + } + setSubmitError(errorMessage); + } finally { + setIsSubmitting(false); + } + } + + return { + step, + setStep, + state, + patch, + reset, + canGoNext: () => canGoNext(step, state), + isSubmitting, + submitError, + submitWarnings, + showConfetti, + showSuccessToast, + setShowSuccessToast, + handleSubmit, + handleClose, + }; +} diff --git a/apps/web/src/components/timeline/AllocationPopover.test.tsx b/apps/web/src/components/timeline/AllocationPopover.test.tsx index 631728a..243f5be 100644 --- a/apps/web/src/components/timeline/AllocationPopover.test.tsx +++ b/apps/web/src/components/timeline/AllocationPopover.test.tsx @@ -1,3 +1,4 @@ +// @vitest-environment node import React from "react"; import { renderToStaticMarkup } from "react-dom/server"; import { beforeEach, describe, expect, it, vi } from "vitest"; @@ -75,7 +76,7 @@ describe("AllocationPopover", () => { />, ); - expect(html).toContain("data-testid=\"timeline-allocation-popover-error\""); + expect(html).toContain('data-testid="timeline-allocation-popover-error"'); expect(html).toContain("The selected booking could not be loaded right now."); expect(html).toContain("Assignment not found"); }); @@ -98,8 +99,10 @@ describe("AllocationPopover", () => { />, ); - expect(html).toContain("data-testid=\"timeline-allocation-popover-unavailable\""); - expect(html).toContain("The selected booking could not be resolved from the current timeline data."); + expect(html).toContain('data-testid="timeline-allocation-popover-unavailable"'); + expect(html).toContain( + "The selected booking could not be resolved from the current timeline data.", + ); }); it("renders a loading skeleton when the allocation is being fetched", () => { @@ -120,7 +123,7 @@ describe("AllocationPopover", () => { />, ); - expect(html).toContain("data-testid=\"timeline-allocation-popover-loading\""); + expect(html).toContain('data-testid="timeline-allocation-popover-loading"'); }); it("renders allocation data when provided as initialAllocation", () => { @@ -160,8 +163,8 @@ describe("AllocationPopover", () => { />, ); - expect(html).not.toContain("data-testid=\"timeline-allocation-popover-error\""); - expect(html).not.toContain("data-testid=\"timeline-allocation-popover-unavailable\""); - expect(html).not.toContain("data-testid=\"timeline-allocation-popover-loading\""); + expect(html).not.toContain('data-testid="timeline-allocation-popover-error"'); + expect(html).not.toContain('data-testid="timeline-allocation-popover-unavailable"'); + expect(html).not.toContain('data-testid="timeline-allocation-popover-loading"'); }); }); diff --git a/apps/web/src/test-utils.tsx b/apps/web/src/test-utils.tsx new file mode 100644 index 0000000..1c3b8a4 --- /dev/null +++ b/apps/web/src/test-utils.tsx @@ -0,0 +1,24 @@ +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { render, type RenderOptions } from "@testing-library/react"; +import type { ReactElement } from "react"; + +function createTestQueryClient() { + return new QueryClient({ + defaultOptions: { + queries: { retry: false, gcTime: 0 }, + mutations: { retry: false }, + }, + }); +} + +function TestProviders({ children }: { children: React.ReactNode }) { + const queryClient = createTestQueryClient(); + return {children}; +} + +function customRender(ui: ReactElement, options?: Omit) { + return render(ui, { wrapper: TestProviders, ...options }); +} + +export * from "@testing-library/react"; +export { customRender as render }; diff --git a/apps/web/src/vitest-setup.ts b/apps/web/src/vitest-setup.ts new file mode 100644 index 0000000..f149f27 --- /dev/null +++ b/apps/web/src/vitest-setup.ts @@ -0,0 +1 @@ +import "@testing-library/jest-dom/vitest"; diff --git a/apps/web/vitest.config.mts b/apps/web/vitest.config.mts index 4c43af1..83f4f73 100644 --- a/apps/web/vitest.config.mts +++ b/apps/web/vitest.config.mts @@ -8,8 +8,9 @@ export default defineConfig({ }, }, test: { - environment: "node", + environment: "jsdom", include: ["src/**/*.{test,spec}.{ts,tsx}"], + setupFiles: ["./src/vitest-setup.ts"], coverage: { provider: "v8", reporter: ["text", "lcov"], diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b201231..049995e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -153,6 +153,15 @@ importers: '@playwright/test': specifier: ^1.49.1 version: 1.58.2 + '@testing-library/jest-dom': + specifier: ^6.9.1 + version: 6.9.1 + '@testing-library/react': + specifier: ^16.3.2 + version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@testing-library/user-event': + specifier: ^14.6.1 + version: 14.6.1(@testing-library/dom@10.4.1) '@types/dompurify': specifier: ^3.2.0 version: 3.2.0 @@ -173,13 +182,16 @@ importers: version: 0.183.1 '@vitest/coverage-v8': specifier: ^2.1.9 - version: 2.1.9(vitest@2.1.9(@types/node@22.19.13)(terser@5.46.1)) + version: 2.1.9(vitest@2.1.9(@types/node@22.19.13)(jsdom@29.0.2(@noble/hashes@2.0.1))(terser@5.46.1)) autoprefixer: specifier: ^10.4.20 version: 10.4.27(postcss@8.5.8) eslint: specifier: ^10.2.0 version: 10.2.0(jiti@1.21.7) + jsdom: + specifier: ^29.0.2 + version: 29.0.2(@noble/hashes@2.0.1) postcss: specifier: ^8.4.49 version: 8.5.8 @@ -191,7 +203,7 @@ importers: version: 5.9.3 vitest: specifier: ^2.1.9 - version: 2.1.9(@types/node@22.19.13)(terser@5.46.1) + version: 2.1.9(@types/node@22.19.13)(jsdom@29.0.2(@noble/hashes@2.0.1))(terser@5.46.1) packages/api: dependencies: @@ -249,7 +261,7 @@ importers: version: 5.9.3 vitest: specifier: ^2.1.8 - version: 2.1.9(@types/node@22.19.13)(terser@5.46.1) + version: 2.1.9(@types/node@22.19.13)(jsdom@29.0.2(@noble/hashes@2.0.1))(terser@5.46.1) packages/application: dependencies: @@ -283,7 +295,7 @@ importers: version: 5.9.3 vitest: specifier: ^2.1.8 - version: 2.1.9(@types/node@22.19.13)(terser@5.46.1) + version: 2.1.9(@types/node@22.19.13)(jsdom@29.0.2(@noble/hashes@2.0.1))(terser@5.46.1) packages/db: dependencies: @@ -336,7 +348,7 @@ importers: version: 5.9.3 vitest: specifier: ^2.1.8 - version: 2.1.9(@types/node@22.19.13)(terser@5.46.1) + version: 2.1.9(@types/node@22.19.13)(jsdom@29.0.2(@noble/hashes@2.0.1))(terser@5.46.1) packages/shared: dependencies: @@ -352,7 +364,7 @@ importers: version: 5.9.3 vitest: specifier: ^2.1.8 - version: 2.1.9(@types/node@22.19.13)(terser@5.46.1) + version: 2.1.9(@types/node@22.19.13)(jsdom@29.0.2(@noble/hashes@2.0.1))(terser@5.46.1) packages/staffing: dependencies: @@ -368,7 +380,7 @@ importers: version: 5.9.3 vitest: specifier: ^2.1.8 - version: 2.1.9(@types/node@22.19.13)(terser@5.46.1) + version: 2.1.9(@types/node@22.19.13)(jsdom@29.0.2(@noble/hashes@2.0.1))(terser@5.46.1) packages/ui: {} @@ -376,22 +388,22 @@ importers: dependencies: '@typescript-eslint/eslint-plugin': specifier: ^8.18.0 - version: 8.56.1(@typescript-eslint/parser@8.56.1(eslint@10.2.0(jiti@1.21.7))(typescript@5.9.3))(eslint@10.2.0(jiti@1.21.7))(typescript@5.9.3) + version: 8.56.1(@typescript-eslint/parser@8.56.1(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3) '@typescript-eslint/parser': specifier: ^8.18.0 - version: 8.56.1(eslint@10.2.0(jiti@1.21.7))(typescript@5.9.3) + version: 8.56.1(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3) eslint: specifier: ^9.0.0 - version: 10.2.0(jiti@1.21.7) + version: 9.39.4(jiti@1.21.7) eslint-config-prettier: specifier: ^9.1.0 - version: 9.1.2(eslint@10.2.0(jiti@1.21.7)) + version: 9.1.2(eslint@9.39.4(jiti@1.21.7)) eslint-plugin-import: specifier: ^2.31.0 - version: 2.32.0(@typescript-eslint/parser@8.56.1(eslint@10.2.0(jiti@1.21.7))(typescript@5.9.3))(eslint@10.2.0(jiti@1.21.7)) + version: 2.32.0(@typescript-eslint/parser@8.56.1(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.4(jiti@1.21.7)) eslint-plugin-react-hooks: specifier: ^7.0.1 - version: 7.0.1(eslint@10.2.0(jiti@1.21.7)) + version: 7.0.1(eslint@9.39.4(jiti@1.21.7)) typescript: specifier: ^5.0.0 version: 5.9.3 @@ -406,6 +418,9 @@ packages: resolution: {integrity: sha512-iscIVt4jWjJ11KEEswgOIOWk8Ew4EFKHRyERJXJ0ouycqzHCtWwb9E5imnxS5rYF1f1IESkFNAfB+h3EkU0Irw==} engines: {node: '>=12'} + '@adobe/css-tools@4.4.4': + resolution: {integrity: sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==} + '@alloc/quick-lru@5.2.0': resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} @@ -414,6 +429,17 @@ packages: resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} + '@asamuzakjp/css-color@5.1.9': + resolution: {integrity: sha512-zd9c/Wdso6v1U7v6w3i/hbAr4K7NaSHImdpvmLt+Y9ea5BhilnIGNkfhOJ7FEIuPipAnE9tZeDOll05WDT0kgg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + '@asamuzakjp/dom-selector@7.0.9': + resolution: {integrity: sha512-r3ElRr7y8ucyN2KdICwGsmj19RoN13CLCa/pvGydghWK6ZzeKQ+TcDjVdtEZz2ElpndM5jXw//B9CEee0mWnVg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + '@asamuzakjp/nwsapi@2.3.9': + resolution: {integrity: sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==} + '@auth/core@0.41.0': resolution: {integrity: sha512-Wd7mHPQ/8zy6Qj7f4T46vg3aoor8fskJm6g2Zyj064oQ3+p0xNZXAV60ww0hY+MbTesfu29kK14Zk5d5JTazXQ==} peerDependencies: @@ -502,6 +528,46 @@ packages: '@bcoe/v8-coverage@0.2.3': resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} + '@bramus/specificity@2.4.2': + resolution: {integrity: sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==} + hasBin: true + + '@csstools/color-helpers@6.0.2': + resolution: {integrity: sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==} + engines: {node: '>=20.19.0'} + + '@csstools/css-calc@3.1.1': + resolution: {integrity: sha512-HJ26Z/vmsZQqs/o3a6bgKslXGFAungXGbinULZO3eMsOyNJHeBBZfup5FiZInOghgoM4Hwnmw+OgbJCNg1wwUQ==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-parser-algorithms': ^4.0.0 + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-color-parser@4.0.2': + resolution: {integrity: sha512-0GEfbBLmTFf0dJlpsNU7zwxRIH0/BGEMuXLTCvFYxuL1tNhqzTbtnFICyJLTNK4a+RechKP75e7w42ClXSnJQw==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-parser-algorithms': ^4.0.0 + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-parser-algorithms@4.0.0': + resolution: {integrity: sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-syntax-patches-for-csstree@1.1.2': + resolution: {integrity: sha512-5GkLzz4prTIpoyeUiIu3iV6CSG3Plo7xRVOFPKI7FVEJ3mZ0A8SwK0XU3Gl7xAkiQ+mDyam+NNp875/C5y+jSA==} + peerDependencies: + css-tree: ^3.2.1 + peerDependenciesMeta: + css-tree: + optional: true + + '@csstools/css-tokenizer@4.0.0': + resolution: {integrity: sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==} + engines: {node: '>=20.19.0'} + '@dimforge/rapier3d-compat@0.12.0': resolution: {integrity: sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow==} @@ -840,26 +906,63 @@ packages: resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + '@eslint/config-array@0.21.2': + resolution: {integrity: sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/config-array@0.23.5': resolution: {integrity: sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} + '@eslint/config-helpers@0.4.2': + resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/config-helpers@0.5.5': resolution: {integrity: sha512-eIJYKTCECbP/nsKaaruF6LW967mtbQbsw4JTtSVkUQc9MneSkbrgPJAbKl9nWr0ZeowV8BfsarBmPpBzGelA2w==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} + '@eslint/core@0.17.0': + resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/core@1.2.1': resolution: {integrity: sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} + '@eslint/eslintrc@3.3.5': + resolution: {integrity: sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.39.4': + resolution: {integrity: sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.7': + resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/object-schema@3.0.5': resolution: {integrity: sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} + '@eslint/plugin-kit@0.4.1': + resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/plugin-kit@0.7.1': resolution: {integrity: sha512-rZAP3aVgB9ds9KOeUSL+zZ21hPmo8dh6fnIFwRQj5EAZl9gzR7wxYbYXYysAM8CTqGmUGyp2S4kUdV17MnGuWQ==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} + '@exodus/bytes@1.15.0': + resolution: {integrity: sha512-UY0nlA+feH81UGSHv92sLEPLCeZFjXOuHhrIo0HQydScuQc8s0A7kL/UdgwgDq8g8ilksmuoF35YVTNphV2aBQ==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + peerDependencies: + '@noble/hashes': ^1.8.0 || ^2.0.0 + peerDependenciesMeta: + '@noble/hashes': + optional: true + '@fast-csv/format@4.3.5': resolution: {integrity: sha512-8iRn6QF3I8Ak78lNAa+Gdl5MJJBM5vRHivFtMRUWINdevNo00K7OXxS2PshawLKTejVwieIlPmK5YlLu6w4u8A==} @@ -1832,6 +1935,35 @@ packages: '@tanstack/virtual-core@3.13.21': resolution: {integrity: sha512-ww+fmLHyCbPSf7JNbWZP3g7wl6SdNo3ah5Aiw+0e9FDErkVHLKprYUrwTm7dF646FtEkN/KkAKPYezxpmvOjxw==} + '@testing-library/dom@10.4.1': + resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} + engines: {node: '>=18'} + + '@testing-library/jest-dom@6.9.1': + resolution: {integrity: sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==} + engines: {node: '>=14', npm: '>=6', yarn: '>=1'} + + '@testing-library/react@16.3.2': + resolution: {integrity: sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==} + engines: {node: '>=18'} + peerDependencies: + '@testing-library/dom': ^10.0.0 + '@types/react': ^18.0.0 || ^19.0.0 + '@types/react-dom': ^18.0.0 || ^19.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@testing-library/user-event@14.6.1': + resolution: {integrity: sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==} + engines: {node: '>=12', npm: '>=6'} + peerDependencies: + '@testing-library/dom': '>=7.21.4' + '@trpc/client@11.11.0': resolution: {integrity: sha512-tIPeetFO8GT/o0In+Lk5JA3f29m8qCBSaKNatQdAx6BOfZFjpqHOaoAsCmE/MdUu9AVhPRPorcoqUlZUdDc5gA==} peerDependencies: @@ -1861,6 +1993,9 @@ packages: '@tybys/wasm-util@0.10.1': resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} + '@types/aria-query@5.0.4': + resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} + '@types/connect@3.4.38': resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} @@ -2182,6 +2317,10 @@ packages: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + ansi-styles@6.2.3: resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} engines: {node: '>=12'} @@ -2208,6 +2347,16 @@ packages: arg@5.0.2: resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + aria-query@5.3.0: + resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} + + aria-query@5.3.2: + resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} + engines: {node: '>= 0.4'} + array-buffer-byte-length@1.0.2: resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} engines: {node: '>= 0.4'} @@ -2355,6 +2504,10 @@ packages: resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} engines: {node: '>= 0.4'} + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + camelcase-css@2.0.1: resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} engines: {node: '>= 6'} @@ -2373,6 +2526,10 @@ packages: chainsaw@0.1.0: resolution: {integrity: sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ==} + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + check-error@2.1.3: resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==} engines: {node: '>= 16'} @@ -2470,6 +2627,13 @@ packages: crypto-js@4.2.0: resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==} + css-tree@3.2.1: + resolution: {integrity: sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + + css.escape@1.5.1: + resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} + cssesc@3.0.0: resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} engines: {node: '>=4'} @@ -2552,6 +2716,10 @@ packages: resolution: {integrity: sha512-QmU3lyEnbENQPo0M1F9BMu4s6cqNNp8iJA+b/HP2sSb7pf3dxwF3+EP1eO69rwBfH9kFJ1apmzrtogAmVt2/Xw==} engines: {node: '>=12'} + data-urls@7.0.0: + resolution: {integrity: sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + data-view-buffer@1.0.2: resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} engines: {node: '>= 0.4'} @@ -2591,6 +2759,9 @@ packages: decimal.js-light@2.5.1: resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==} + decimal.js@10.6.0: + resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} + deep-eql@5.0.2: resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} engines: {node: '>=6'} @@ -2610,6 +2781,10 @@ packages: resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==} engines: {node: '>=0.10'} + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + detect-libc@2.1.2: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} @@ -2630,6 +2805,12 @@ packages: resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} engines: {node: '>=0.10.0'} + dom-accessibility-api@0.5.16: + resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} + + dom-accessibility-api@0.6.3: + resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==} + dompurify@3.3.3: resolution: {integrity: sha512-Oj6pzI2+RqBfFG+qOaOLbFXLQ90ARpcGG6UePL82bJLtdsa6CYJD7nmiU8MW9nQNOtCHV3lZ/Bzq1X0QYbBZCA==} @@ -2669,6 +2850,10 @@ packages: resolution: {integrity: sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==} engines: {node: '>=10.13.0'} + entities@6.0.1: + resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} + engines: {node: '>=0.12'} + environment@1.1.0: resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} engines: {node: '>=18'} @@ -2778,6 +2963,10 @@ packages: resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} engines: {node: '>=8.0.0'} + eslint-scope@8.4.0: + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + eslint-scope@9.1.2: resolution: {integrity: sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} @@ -2786,6 +2975,10 @@ packages: resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + eslint-visitor-keys@5.0.1: resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} @@ -2800,6 +2993,20 @@ packages: jiti: optional: true + eslint@9.39.4: + resolution: {integrity: sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@10.4.0: + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + espree@11.2.0: resolution: {integrity: sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} @@ -3027,6 +3234,10 @@ packages: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + globalthis@1.0.4: resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} engines: {node: '>= 0.4'} @@ -3077,6 +3288,10 @@ packages: hsl-to-rgb-for-reals@1.1.1: resolution: {integrity: sha512-LgOWAkrN0rFaQpfdWBQlv/VhkOxb5AsBjk6NQVx4yEzWS923T07X0M1Y0VNko2H52HeSpZrZNNMJ0aFqsdVzQg==} + html-encoding-sniffer@6.0.0: + resolution: {integrity: sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + html-escaper@2.0.2: resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} @@ -3112,6 +3327,10 @@ packages: immer@11.1.4: resolution: {integrity: sha512-XREFCPo6ksxVzP4E0ekD5aMdf8WMwmdNaz6vuvxgI40UaEiu6q3p8X52aU6GdyvLY3XXX/8R7JOTXStz/nBbRw==} + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + import-in-the-middle@2.0.6: resolution: {integrity: sha512-3vZV3jX0XRFW3EJDTwzWoZa+RH1b8eTTx6YOCjglrLyPuepwoBti1k3L2dKwdCUrnVEfc5CuRuGstaC/uQJJaw==} @@ -3123,6 +3342,10 @@ packages: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} + indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + inflight@1.0.6: resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. @@ -3221,6 +3444,9 @@ packages: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} + is-potential-custom-element-name@1.0.1: + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + is-reference@1.2.1: resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==} @@ -3312,6 +3538,19 @@ packages: js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + hasBin: true + + jsdom@29.0.2: + resolution: {integrity: sha512-9VnGEBosc/ZpwyOsJBCQ/3I5p7Q5ngOY14a9bf5btenAORmZfDse1ZEheMiWcJ3h81+Fv7HmJFdS0szo/waF2w==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24.0.0} + peerDependencies: + canvas: ^3.0.0 + peerDependenciesMeta: + canvas: + optional: true + jsesc@3.1.0: resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} engines: {node: '>=6'} @@ -3436,6 +3675,9 @@ packages: lodash.isundefined@3.0.1: resolution: {integrity: sha512-MXB1is3s899/cD8jheYYE2V9qTHwKvt+npCwpD+1Sxm3Q3cECXCiYHjeHWXNwr6Q0SOBPrYUDxendrO6goVTEA==} + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + lodash.union@4.6.0: resolution: {integrity: sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==} @@ -3463,6 +3705,10 @@ packages: lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + lz-string@1.5.0: + resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} + hasBin: true + magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} @@ -3477,6 +3723,9 @@ packages: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} + mdn-data@2.27.1: + resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==} + media-engine@1.0.3: resolution: {integrity: sha512-aa5tG6sDoK+k70B9iEX1NeyfT8ObCKhNDs6lJVpwF6r8vhUfuKMslIcirq6HIUYuuUYLefcEQOn9bSBOvawtwg==} @@ -3506,6 +3755,10 @@ packages: resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} engines: {node: '>=18'} + min-indent@1.0.1: + resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} + engines: {node: '>=4'} + minimatch@10.2.4: resolution: {integrity: sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==} engines: {node: 18 || 20 || >=22} @@ -3731,9 +3984,16 @@ packages: pako@1.0.11: resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + parse-svg-path@0.1.2: resolution: {integrity: sha512-JyPSBnkTJ0AI8GGJLfMXvKq42cj5c006fnLz6fXy6zfoVjJizi8BNTpu8on8ziI1cKy9d9DGNuY17Ce7wuejpQ==} + parse5@8.0.0: + resolution: {integrity: sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==} + path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -3906,6 +4166,10 @@ packages: engines: {node: '>=14'} hasBin: true + pretty-format@27.5.1: + resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + prisma@5.22.0: resolution: {integrity: sha512-vtpjW3XuYCSnMsNVBjLMNkTj6OZbudcPPTPYHqX0CJfpcdWciI1dM8uHETwmDxxiqEwCIE6WvXucWUetJgfu/A==} engines: {node: '>=16.13'} @@ -3971,6 +4235,9 @@ packages: react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + react-is@17.0.2: + resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + react-kapsule@2.5.7: resolution: {integrity: sha512-kifAF4ZPD77qZKc4CKLmozq6GY1sBzPEJTIJb0wWFK6HsePJatK3jXplZn2eeAt3x67CDozgi7/rO8fNQ/AL7A==} engines: {node: '>=12'} @@ -4028,6 +4295,10 @@ packages: react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-is: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + redent@3.0.0: + resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} + engines: {node: '>=8'} + redis-errors@1.2.0: resolution: {integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==} engines: {node: '>=4'} @@ -4073,6 +4344,10 @@ packages: resize-observer-polyfill@1.5.1: resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==} + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} @@ -4134,6 +4409,10 @@ packages: resolution: {integrity: sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==} engines: {node: '>=10'} + saxes@6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} + scheduler@0.25.0-rc-603e6108-20241029: resolution: {integrity: sha512-pFwF6H1XrSdYYNLfOcGlM28/j8CGLu8IvdrxqhjWULe2bPcKiKW4CV+OWqR/9fT52mywx65l7ysNkjLKBda7eA==} @@ -4302,6 +4581,14 @@ packages: resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} engines: {node: '>=4'} + strip-indent@3.0.0: + resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} + engines: {node: '>=8'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + styled-jsx@5.1.6: resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==} engines: {node: '>= 12.0.0'} @@ -4335,6 +4622,9 @@ packages: svg-arc-to-cubic-bezier@3.2.0: resolution: {integrity: sha512-djbJ/vZKZO+gPoSDThGNpKDO+o+bAeA4XQKovvkNCqnIS2t+S4qnLAGQhyyrulhCFRl1WWzAp0wUDV8PpTVU3g==} + symbol-tree@3.2.4: + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + tailwind-merge@2.6.1: resolution: {integrity: sha512-Oo6tHdpZsGpkKG88HJ8RR1rg/RdnEkQEfMoEk2x1XRI3F1AxeU+ijRXpiVUF4UbLfcxxRGw6TbUINKYdWVsQTQ==} @@ -4437,6 +4727,13 @@ packages: resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} engines: {node: '>=14.0.0'} + tldts-core@7.0.28: + resolution: {integrity: sha512-7W5Efjhsc3chVdFhqtaU0KtK32J37Zcr9RKtID54nG+tIpcY79CQK/veYPODxtD/LJ4Lue66jvrQzIX2Z2/pUQ==} + + tldts@7.0.28: + resolution: {integrity: sha512-+Zg3vWhRUv8B1maGSTFdev9mjoo8Etn2Ayfs4cnjlD3CsGkxXX4QyW3j2WJ0wdjYcYmy7Lx2RDsZMhgCWafKIw==} + hasBin: true + tmp@0.2.5: resolution: {integrity: sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==} engines: {node: '>=14.14'} @@ -4445,9 +4742,17 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} + tough-cookie@6.0.1: + resolution: {integrity: sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==} + engines: {node: '>=16'} + tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + tr46@6.0.0: + resolution: {integrity: sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==} + engines: {node: '>=20'} + traverse@0.3.9: resolution: {integrity: sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==} @@ -4541,6 +4846,10 @@ packages: undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + undici@7.24.7: + resolution: {integrity: sha512-H/nlJ/h0ggGC+uRL3ovD+G0i4bqhvsDOpbDv7At5eFLlj2b41L8QliGbnl2H7SnDiYhENphh1tQFJZf+MyfLsQ==} + engines: {node: '>=20.18.1'} + unicode-properties@1.4.1: resolution: {integrity: sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==} @@ -4643,6 +4952,10 @@ packages: jsdom: optional: true + w3c-xmlserializer@5.0.0: + resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} + engines: {node: '>=18'} + watchpack@2.5.1: resolution: {integrity: sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==} engines: {node: '>=10.13.0'} @@ -4650,6 +4963,10 @@ packages: webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + webidl-conversions@8.0.1: + resolution: {integrity: sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==} + engines: {node: '>=20'} + webpack-sources@3.3.4: resolution: {integrity: sha512-7tP1PdV4vF+lYPnkMR0jMY5/la2ub5Fc/8VQrrU+lXkiM6C4TjVfGw7iKfyhnTQOsD+6Q/iKw0eFciziRgD58Q==} engines: {node: '>=10.13.0'} @@ -4664,6 +4981,14 @@ packages: webpack-cli: optional: true + whatwg-mimetype@5.0.0: + resolution: {integrity: sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==} + engines: {node: '>=20'} + + whatwg-url@16.0.1: + resolution: {integrity: sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} @@ -4719,6 +5044,10 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + xml-name-validator@5.0.0: + resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} + engines: {node: '>=18'} + xmlchars@2.2.0: resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} @@ -4775,6 +5104,8 @@ snapshots: three-forcegraph: 1.43.1(three@0.183.2) three-render-objects: 1.40.5(three@0.183.2) + '@adobe/css-tools@4.4.4': {} + '@alloc/quick-lru@5.2.0': {} '@ampproject/remapping@2.3.0': @@ -4782,6 +5113,22 @@ snapshots: '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 + '@asamuzakjp/css-color@5.1.9': + dependencies: + '@csstools/css-calc': 3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-color-parser': 4.0.2(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + + '@asamuzakjp/dom-selector@7.0.9': + dependencies: + '@asamuzakjp/nwsapi': 2.3.9 + bidi-js: 1.0.3 + css-tree: 3.2.1 + is-potential-custom-element-name: 1.0.1 + + '@asamuzakjp/nwsapi@2.3.9': {} + '@auth/core@0.41.0': dependencies: '@panva/hkdf': 1.2.1 @@ -4894,6 +5241,34 @@ snapshots: '@bcoe/v8-coverage@0.2.3': {} + '@bramus/specificity@2.4.2': + dependencies: + css-tree: 3.2.1 + + '@csstools/color-helpers@6.0.2': {} + + '@csstools/css-calc@3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + + '@csstools/css-color-parser@4.0.2(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/color-helpers': 6.0.2 + '@csstools/css-calc': 3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + + '@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/css-tokenizer': 4.0.0 + + '@csstools/css-syntax-patches-for-csstree@1.1.2(css-tree@3.2.1)': + optionalDependencies: + css-tree: 3.2.1 + + '@csstools/css-tokenizer@4.0.0': {} + '@dimforge/rapier3d-compat@0.12.0': {} '@dnd-kit/accessibility@3.1.1(react@19.2.4)': @@ -5089,8 +5464,21 @@ snapshots: eslint: 10.2.0(jiti@1.21.7) eslint-visitor-keys: 3.4.3 + '@eslint-community/eslint-utils@4.9.1(eslint@9.39.4(jiti@1.21.7))': + dependencies: + eslint: 9.39.4(jiti@1.21.7) + eslint-visitor-keys: 3.4.3 + '@eslint-community/regexpp@4.12.2': {} + '@eslint/config-array@0.21.2': + dependencies: + '@eslint/object-schema': 2.1.7 + debug: 4.4.3 + minimatch: 3.1.5 + transitivePeerDependencies: + - supports-color + '@eslint/config-array@0.23.5': dependencies: '@eslint/object-schema': 3.0.5 @@ -5099,21 +5487,56 @@ snapshots: transitivePeerDependencies: - supports-color + '@eslint/config-helpers@0.4.2': + dependencies: + '@eslint/core': 0.17.0 + '@eslint/config-helpers@0.5.5': dependencies: '@eslint/core': 1.2.1 + '@eslint/core@0.17.0': + dependencies: + '@types/json-schema': 7.0.15 + '@eslint/core@1.2.1': dependencies: '@types/json-schema': 7.0.15 + '@eslint/eslintrc@3.3.5': + dependencies: + ajv: 6.14.0 + debug: 4.4.3 + espree: 10.4.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.1 + minimatch: 3.1.5 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@9.39.4': {} + + '@eslint/object-schema@2.1.7': {} + '@eslint/object-schema@3.0.5': {} + '@eslint/plugin-kit@0.4.1': + dependencies: + '@eslint/core': 0.17.0 + levn: 0.4.1 + '@eslint/plugin-kit@0.7.1': dependencies: '@eslint/core': 1.2.1 levn: 0.4.1 + '@exodus/bytes@1.15.0(@noble/hashes@2.0.1)': + optionalDependencies: + '@noble/hashes': 2.0.1 + '@fast-csv/format@4.3.5': dependencies: '@types/node': 14.18.63 @@ -6131,6 +6554,40 @@ snapshots: '@tanstack/virtual-core@3.13.21': {} + '@testing-library/dom@10.4.1': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/runtime': 7.28.6 + '@types/aria-query': 5.0.4 + aria-query: 5.3.0 + dom-accessibility-api: 0.5.16 + lz-string: 1.5.0 + picocolors: 1.1.1 + pretty-format: 27.5.1 + + '@testing-library/jest-dom@6.9.1': + dependencies: + '@adobe/css-tools': 4.4.4 + aria-query: 5.3.2 + css.escape: 1.5.1 + dom-accessibility-api: 0.6.3 + picocolors: 1.1.1 + redent: 3.0.0 + + '@testing-library/react@16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@babel/runtime': 7.28.6 + '@testing-library/dom': 10.4.1 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@testing-library/user-event@14.6.1(@testing-library/dom@10.4.1)': + dependencies: + '@testing-library/dom': 10.4.1 + '@trpc/client@11.11.0(@trpc/server@11.11.0(typescript@5.9.3))(typescript@5.9.3)': dependencies: '@trpc/server': 11.11.0(typescript@5.9.3) @@ -6157,6 +6614,8 @@ snapshots: tslib: 2.8.1 optional: true + '@types/aria-query@5.0.4': {} + '@types/connect@3.4.38': dependencies: '@types/node': 22.19.13 @@ -6273,15 +6732,15 @@ snapshots: '@types/webxr@0.5.24': {} - '@typescript-eslint/eslint-plugin@8.56.1(@typescript-eslint/parser@8.56.1(eslint@10.2.0(jiti@1.21.7))(typescript@5.9.3))(eslint@10.2.0(jiti@1.21.7))(typescript@5.9.3)': + '@typescript-eslint/eslint-plugin@8.56.1(@typescript-eslint/parser@8.56.1(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.56.1(eslint@10.2.0(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/parser': 8.56.1(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3) '@typescript-eslint/scope-manager': 8.56.1 - '@typescript-eslint/type-utils': 8.56.1(eslint@10.2.0(jiti@1.21.7))(typescript@5.9.3) - '@typescript-eslint/utils': 8.56.1(eslint@10.2.0(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/type-utils': 8.56.1(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/utils': 8.56.1(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3) '@typescript-eslint/visitor-keys': 8.56.1 - eslint: 10.2.0(jiti@1.21.7) + eslint: 9.39.4(jiti@1.21.7) ignore: 7.0.5 natural-compare: 1.4.0 ts-api-utils: 2.4.0(typescript@5.9.3) @@ -6289,14 +6748,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.56.1(eslint@10.2.0(jiti@1.21.7))(typescript@5.9.3)': + '@typescript-eslint/parser@8.56.1(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3)': dependencies: '@typescript-eslint/scope-manager': 8.56.1 '@typescript-eslint/types': 8.56.1 '@typescript-eslint/typescript-estree': 8.56.1(typescript@5.9.3) '@typescript-eslint/visitor-keys': 8.56.1 debug: 4.4.3 - eslint: 10.2.0(jiti@1.21.7) + eslint: 9.39.4(jiti@1.21.7) typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -6319,13 +6778,13 @@ snapshots: dependencies: typescript: 5.9.3 - '@typescript-eslint/type-utils@8.56.1(eslint@10.2.0(jiti@1.21.7))(typescript@5.9.3)': + '@typescript-eslint/type-utils@8.56.1(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3)': dependencies: '@typescript-eslint/types': 8.56.1 '@typescript-eslint/typescript-estree': 8.56.1(typescript@5.9.3) - '@typescript-eslint/utils': 8.56.1(eslint@10.2.0(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/utils': 8.56.1(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3) debug: 4.4.3 - eslint: 10.2.0(jiti@1.21.7) + eslint: 9.39.4(jiti@1.21.7) ts-api-utils: 2.4.0(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: @@ -6348,13 +6807,13 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.56.1(eslint@10.2.0(jiti@1.21.7))(typescript@5.9.3)': + '@typescript-eslint/utils@8.56.1(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3)': dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.2.0(jiti@1.21.7)) + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@1.21.7)) '@typescript-eslint/scope-manager': 8.56.1 '@typescript-eslint/types': 8.56.1 '@typescript-eslint/typescript-estree': 8.56.1(typescript@5.9.3) - eslint: 10.2.0(jiti@1.21.7) + eslint: 9.39.4(jiti@1.21.7) typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -6364,7 +6823,7 @@ snapshots: '@typescript-eslint/types': 8.56.1 eslint-visitor-keys: 5.0.1 - '@vitest/coverage-v8@2.1.9(vitest@2.1.9(@types/node@22.19.13)(terser@5.46.1))': + '@vitest/coverage-v8@2.1.9(vitest@2.1.9(@types/node@22.19.13)(jsdom@29.0.2(@noble/hashes@2.0.1))(terser@5.46.1))': dependencies: '@ampproject/remapping': 2.3.0 '@bcoe/v8-coverage': 0.2.3 @@ -6378,7 +6837,7 @@ snapshots: std-env: 3.10.0 test-exclude: 7.0.2 tinyrainbow: 1.2.0 - vitest: 2.1.9(@types/node@22.19.13)(terser@5.46.1) + vitest: 2.1.9(@types/node@22.19.13)(jsdom@29.0.2(@noble/hashes@2.0.1))(terser@5.46.1) transitivePeerDependencies: - supports-color @@ -6563,6 +7022,8 @@ snapshots: dependencies: color-convert: 2.0.1 + ansi-styles@5.2.0: {} + ansi-styles@6.2.3: {} any-promise@1.3.0: {} @@ -6610,6 +7071,14 @@ snapshots: arg@5.0.2: {} + argparse@2.0.1: {} + + aria-query@5.3.0: + dependencies: + dequal: 2.0.3 + + aria-query@5.3.2: {} + array-buffer-byte-length@1.0.2: dependencies: call-bound: 1.0.4 @@ -6777,6 +7246,8 @@ snapshots: call-bind-apply-helpers: 1.0.2 get-intrinsic: 1.3.0 + callsites@3.1.0: {} + camelcase-css@2.0.1: {} camelcase@5.3.1: {} @@ -6795,6 +7266,11 @@ snapshots: dependencies: traverse: 0.3.9 + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + check-error@2.1.3: {} chokidar@3.6.0: @@ -6885,6 +7361,13 @@ snapshots: crypto-js@4.2.0: {} + css-tree@3.2.1: + dependencies: + mdn-data: 2.27.1 + source-map-js: 1.2.1 + + css.escape@1.5.1: {} + cssesc@3.0.0: {} csstype@3.2.3: {} @@ -6954,6 +7437,13 @@ snapshots: dependencies: accessor-fn: 1.5.3 + data-urls@7.0.0(@noble/hashes@2.0.1): + dependencies: + whatwg-mimetype: 5.0.0 + whatwg-url: 16.0.1(@noble/hashes@2.0.1) + transitivePeerDependencies: + - '@noble/hashes' + data-view-buffer@1.0.2: dependencies: call-bound: 1.0.4 @@ -6986,6 +7476,8 @@ snapshots: decimal.js-light@2.5.1: {} + decimal.js@10.6.0: {} + deep-eql@5.0.2: {} deep-is@0.1.4: {} @@ -7004,6 +7496,8 @@ snapshots: denque@2.1.0: {} + dequal@2.0.3: {} + detect-libc@2.1.2: optional: true @@ -7019,6 +7513,10 @@ snapshots: dependencies: esutils: 2.0.3 + dom-accessibility-api@0.5.16: {} + + dom-accessibility-api@0.6.3: {} + dompurify@3.3.3: optionalDependencies: '@types/trusted-types': 2.0.7 @@ -7056,6 +7554,8 @@ snapshots: graceful-fs: 4.2.11 tapable: 2.3.0 + entities@6.0.1: {} + environment@1.1.0: {} es-abstract@1.24.1: @@ -7205,9 +7705,9 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-config-prettier@9.1.2(eslint@10.2.0(jiti@1.21.7)): + eslint-config-prettier@9.1.2(eslint@9.39.4(jiti@1.21.7)): dependencies: - eslint: 10.2.0(jiti@1.21.7) + eslint: 9.39.4(jiti@1.21.7) eslint-import-resolver-node@0.3.9: dependencies: @@ -7217,17 +7717,17 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.56.1(eslint@10.2.0(jiti@1.21.7))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@10.2.0(jiti@1.21.7)): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.56.1(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.4(jiti@1.21.7)): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 8.56.1(eslint@10.2.0(jiti@1.21.7))(typescript@5.9.3) - eslint: 10.2.0(jiti@1.21.7) + '@typescript-eslint/parser': 8.56.1(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3) + eslint: 9.39.4(jiti@1.21.7) eslint-import-resolver-node: 0.3.9 transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.56.1(eslint@10.2.0(jiti@1.21.7))(typescript@5.9.3))(eslint@10.2.0(jiti@1.21.7)): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.56.1(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.4(jiti@1.21.7)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -7236,9 +7736,9 @@ snapshots: array.prototype.flatmap: 1.3.3 debug: 3.2.7 doctrine: 2.1.0 - eslint: 10.2.0(jiti@1.21.7) + eslint: 9.39.4(jiti@1.21.7) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.56.1(eslint@10.2.0(jiti@1.21.7))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@10.2.0(jiti@1.21.7)) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.56.1(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.4(jiti@1.21.7)) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -7250,17 +7750,17 @@ snapshots: string.prototype.trimend: 1.0.9 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.56.1(eslint@10.2.0(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/parser': 8.56.1(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color - eslint-plugin-react-hooks@7.0.1(eslint@10.2.0(jiti@1.21.7)): + eslint-plugin-react-hooks@7.0.1(eslint@9.39.4(jiti@1.21.7)): dependencies: '@babel/core': 7.29.0 '@babel/parser': 7.29.2 - eslint: 10.2.0(jiti@1.21.7) + eslint: 9.39.4(jiti@1.21.7) hermes-parser: 0.25.1 zod: 3.25.76 zod-validation-error: 4.0.2(zod@3.25.76) @@ -7272,6 +7772,11 @@ snapshots: esrecurse: 4.3.0 estraverse: 4.3.0 + eslint-scope@8.4.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + eslint-scope@9.1.2: dependencies: '@types/esrecurse': 4.3.1 @@ -7281,6 +7786,8 @@ snapshots: eslint-visitor-keys@3.4.3: {} + eslint-visitor-keys@4.2.1: {} + eslint-visitor-keys@5.0.1: {} eslint@10.2.0(jiti@1.21.7): @@ -7320,6 +7827,53 @@ snapshots: transitivePeerDependencies: - supports-color + eslint@9.39.4(jiti@1.21.7): + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@1.21.7)) + '@eslint-community/regexpp': 4.12.2 + '@eslint/config-array': 0.21.2 + '@eslint/config-helpers': 0.4.2 + '@eslint/core': 0.17.0 + '@eslint/eslintrc': 3.3.5 + '@eslint/js': 9.39.4 + '@eslint/plugin-kit': 0.4.1 + '@humanfs/node': 0.16.7 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.8 + ajv: 6.14.0 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + esquery: 1.7.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.5 + natural-compare: 1.4.0 + optionator: 0.9.4 + optionalDependencies: + jiti: 1.21.7 + transitivePeerDependencies: + - supports-color + + espree@10.4.0: + dependencies: + acorn: 8.16.0 + acorn-jsx: 5.3.2(acorn@8.16.0) + eslint-visitor-keys: 4.2.1 + espree@11.2.0: dependencies: acorn: 8.16.0 @@ -7562,6 +8116,8 @@ snapshots: once: 1.4.0 path-is-absolute: 1.0.1 + globals@14.0.0: {} + globalthis@1.0.4: dependencies: define-properties: 1.2.1 @@ -7605,6 +8161,12 @@ snapshots: hsl-to-rgb-for-reals@1.1.1: {} + html-encoding-sniffer@6.0.0(@noble/hashes@2.0.1): + dependencies: + '@exodus/bytes': 1.15.0(@noble/hashes@2.0.1) + transitivePeerDependencies: + - '@noble/hashes' + html-escaper@2.0.2: {} https-proxy-agent@5.0.1: @@ -7630,6 +8192,11 @@ snapshots: immer@11.1.4: {} + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + import-in-the-middle@2.0.6: dependencies: acorn: 8.16.0 @@ -7646,6 +8213,8 @@ snapshots: imurmurhash@0.1.4: {} + indent-string@4.0.0: {} + inflight@1.0.6: dependencies: once: 1.4.0 @@ -7756,6 +8325,8 @@ snapshots: is-number@7.0.0: {} + is-potential-custom-element-name@1.0.1: {} + is-reference@1.2.1: dependencies: '@types/estree': 1.0.8 @@ -7852,6 +8423,36 @@ snapshots: js-tokens@4.0.0: {} + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + + jsdom@29.0.2(@noble/hashes@2.0.1): + dependencies: + '@asamuzakjp/css-color': 5.1.9 + '@asamuzakjp/dom-selector': 7.0.9 + '@bramus/specificity': 2.4.2 + '@csstools/css-syntax-patches-for-csstree': 1.1.2(css-tree@3.2.1) + '@exodus/bytes': 1.15.0(@noble/hashes@2.0.1) + css-tree: 3.2.1 + data-urls: 7.0.0(@noble/hashes@2.0.1) + decimal.js: 10.6.0 + html-encoding-sniffer: 6.0.0(@noble/hashes@2.0.1) + is-potential-custom-element-name: 1.0.1 + lru-cache: 11.2.7 + parse5: 8.0.0 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 6.0.1 + undici: 7.24.7 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 8.0.1 + whatwg-mimetype: 5.0.0 + whatwg-url: 16.0.1(@noble/hashes@2.0.1) + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - '@noble/hashes' + jsesc@3.1.0: {} json-buffer@3.0.1: {} @@ -7963,6 +8564,8 @@ snapshots: lodash.isundefined@3.0.1: {} + lodash.merge@4.6.2: {} + lodash.union@4.6.0: {} lodash.uniq@4.5.0: {} @@ -7989,6 +8592,8 @@ snapshots: dependencies: yallist: 3.1.1 + lz-string@1.5.0: {} + magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -8005,6 +8610,8 @@ snapshots: math-intrinsics@1.1.0: {} + mdn-data@2.27.1: {} + media-engine@1.0.3: {} merge-stream@2.0.0: {} @@ -8026,6 +8633,8 @@ snapshots: mimic-function@5.0.1: {} + min-indent@1.0.1: {} + minimatch@10.2.4: dependencies: brace-expansion: 5.0.4 @@ -8229,8 +8838,16 @@ snapshots: pako@1.0.11: {} + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + parse-svg-path@0.1.2: {} + parse5@8.0.0: + dependencies: + entities: 6.0.1 + path-exists@4.0.0: {} path-is-absolute@1.0.1: {} @@ -8374,6 +8991,12 @@ snapshots: prettier@3.8.1: {} + pretty-format@27.5.1: + dependencies: + ansi-regex: 5.0.1 + ansi-styles: 5.2.0 + react-is: 17.0.2 + prisma@5.22.0: dependencies: '@prisma/engines': 5.22.0 @@ -8442,6 +9065,8 @@ snapshots: react-is@16.13.1: {} + react-is@17.0.2: {} + react-kapsule@2.5.7(react@19.2.4): dependencies: jerrypick: 1.1.2 @@ -8515,6 +9140,11 @@ snapshots: - '@types/react' - redux + redent@3.0.0: + dependencies: + indent-string: 4.0.0 + strip-indent: 3.0.0 + redis-errors@1.2.0: {} redis-parser@3.0.0: @@ -8564,6 +9194,8 @@ snapshots: resize-observer-polyfill@1.5.1: {} + resolve-from@4.0.0: {} + resolve-pkg-maps@1.0.0: {} resolve@1.22.11: @@ -8651,6 +9283,10 @@ snapshots: dependencies: xmlchars: 2.2.0 + saxes@6.0.0: + dependencies: + xmlchars: 2.2.0 + scheduler@0.25.0-rc-603e6108-20241029: {} scheduler@0.27.0: {} @@ -8872,6 +9508,12 @@ snapshots: strip-bom@3.0.0: {} + strip-indent@3.0.0: + dependencies: + min-indent: 1.0.1 + + strip-json-comments@3.1.1: {} + styled-jsx@5.1.6(react@19.2.4): dependencies: client-only: 0.0.1 @@ -8899,6 +9541,8 @@ snapshots: svg-arc-to-cubic-bezier@3.2.0: {} + symbol-tree@3.2.4: {} + tailwind-merge@2.6.1: {} tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.3): @@ -9020,14 +9664,28 @@ snapshots: tinyspy@3.0.2: {} + tldts-core@7.0.28: {} + + tldts@7.0.28: + dependencies: + tldts-core: 7.0.28 + tmp@0.2.5: {} to-regex-range@5.0.1: dependencies: is-number: 7.0.0 + tough-cookie@6.0.1: + dependencies: + tldts: 7.0.28 + tr46@0.0.3: {} + tr46@6.0.0: + dependencies: + punycode: 2.3.1 + traverse@0.3.9: {} ts-api-utils@2.4.0(typescript@5.9.3): @@ -9129,6 +9787,8 @@ snapshots: undici-types@6.21.0: {} + undici@7.24.7: {} + unicode-properties@1.4.1: dependencies: base64-js: 1.5.1 @@ -9223,7 +9883,7 @@ snapshots: fsevents: 2.3.3 terser: 5.46.1 - vitest@2.1.9(@types/node@22.19.13)(terser@5.46.1): + vitest@2.1.9(@types/node@22.19.13)(jsdom@29.0.2(@noble/hashes@2.0.1))(terser@5.46.1): dependencies: '@vitest/expect': 2.1.9 '@vitest/mocker': 2.1.9(vite@5.4.21(@types/node@22.19.13)(terser@5.46.1)) @@ -9247,6 +9907,7 @@ snapshots: why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 22.19.13 + jsdom: 29.0.2(@noble/hashes@2.0.1) transitivePeerDependencies: - less - lightningcss @@ -9258,6 +9919,10 @@ snapshots: - supports-color - terser + w3c-xmlserializer@5.0.0: + dependencies: + xml-name-validator: 5.0.0 + watchpack@2.5.1: dependencies: glob-to-regexp: 0.4.1 @@ -9265,6 +9930,8 @@ snapshots: webidl-conversions@3.0.1: {} + webidl-conversions@8.0.1: {} + webpack-sources@3.3.4: {} webpack@5.105.4: @@ -9299,6 +9966,16 @@ snapshots: - esbuild - uglify-js + whatwg-mimetype@5.0.0: {} + + whatwg-url@16.0.1(@noble/hashes@2.0.1): + dependencies: + '@exodus/bytes': 1.15.0(@noble/hashes@2.0.1) + tr46: 6.0.0 + webidl-conversions: 8.0.1 + transitivePeerDependencies: + - '@noble/hashes' + whatwg-url@5.0.0: dependencies: tr46: 0.0.3 @@ -9384,6 +10061,8 @@ snapshots: wrappy@1.0.2: {} + xml-name-validator@5.0.0: {} + xmlchars@2.2.0: {} xtend@4.0.2: {}