+ {/* Backdrop */}
+
setIsOpen(false)}
+ aria-hidden="true"
+ />
+
+ {/* Drawer Content */}
+
+
+ )}
+ >
+ );
+}
diff --git a/app/components/AIAssistant/Composer.tsx b/app/components/AIAssistant/Composer.tsx
new file mode 100644
index 0000000..0ccac04
--- /dev/null
+++ b/app/components/AIAssistant/Composer.tsx
@@ -0,0 +1,72 @@
+"use client";
+
+import { ComposerPrimitive } from "@assistant-ui/react";
+import { Send, Square, Mic, MicOff } from "lucide-react";
+import { useSpeechToText } from "@/hooks/use-speech-to-text";
+
+export function Composer() {
+ const { isListening, toggleListening, isSupported } = useSpeechToText();
+
+ return (
+
+
+
+
+
+ {isSupported && (
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Enter to send,{" "}
+ Shift+Enter for newline
+
+ {isListening && (
+
+
+ Listening...
+
+ )}
+
+
+ );
+}
diff --git a/app/components/AIAssistant/Thread.tsx b/app/components/AIAssistant/Thread.tsx
new file mode 100644
index 0000000..700e9f1
--- /dev/null
+++ b/app/components/AIAssistant/Thread.tsx
@@ -0,0 +1,274 @@
+"use client";
+
+import { useState } from "react";
+import {
+ ThreadPrimitive,
+ MessagePrimitive,
+ ActionBarPrimitive,
+ BranchPickerPrimitive,
+ AuiIf,
+} from "@assistant-ui/react";
+import { MarkdownTextPrimitive } from "@assistant-ui/react-markdown";
+import { Bot, User, Copy, Check, RotateCcw, ChevronLeft, ChevronRight, Sparkles, BookOpen, Sun, Cpu } from "lucide-react";
+import { Composer } from "@/components/AIAssistant/Composer";
+
+export interface CodeBlockProps {
+ readonly code: string;
+ readonly language?: string;
+}
+
+export function CodeBlock({ code, language }: Readonly
) {
+ const [copied, setCopied] = useState(false);
+
+ const handleCopy = async () => {
+ try {
+ await navigator.clipboard.writeText(code);
+ setCopied(true);
+ setTimeout(() => setCopied(false), 2000);
+ } catch {
+ // Fallback ignore
+ }
+ };
+
+ return (
+
+
+ {language || "text"}
+
+
+
+ {code}
+
+
+ );
+}
+
+export function SuggestedPrompts() {
+ const prompts = [
+ {
+ label: "Search documentation",
+ prompt: "Search documentation for getting started",
+ icon: BookOpen,
+ },
+ {
+ label: "Switch to dark mode",
+ prompt: "Switch theme to dark mode",
+ icon: Sun,
+ },
+ {
+ label: "Show system info",
+ prompt: "What is the cur8d stack and system info?",
+ icon: Cpu,
+ },
+ {
+ label: "How to test",
+ prompt: "How do I run tests and check 80% coverage?",
+ icon: Sparkles,
+ },
+ ];
+
+ return (
+
+
+
+
+
How can I assist you today?
+
+ Ask about the template, search docs, toggle themes, or run tools with AI.
+
+
+
+ {prompts.map((p) => {
+ const Icon = p.icon;
+ return (
+
+
+
+ );
+ })}
+
+
+ );
+}
+
+export function UserMessage() {
+ return (
+
+
+
+
+
+
+
+
+ /
+
+
+
+
+
+
+ );
+}
+
+export interface MarkdownCodeProps extends React.ComponentPropsWithoutRef<"code"> {
+ readonly inline?: boolean;
+}
+
+export function MarkdownCode({ inline, className, children, ...props }: Readonly) {
+ const match = /language-(\w+)/.exec(className || "");
+ if (!inline && match) {
+ let codeString = "";
+ if (Array.isArray(children)) {
+ codeString = children.join("");
+ } else if (typeof children === "string") {
+ codeString = children;
+ }
+ return ;
+ }
+ return (
+
+ {children}
+
+ );
+}
+
+export function MarkdownParagraph({ children }: Readonly<{ readonly children?: React.ReactNode }>) {
+ return {children}
;
+}
+
+export function MarkdownUnorderedList({ children }: Readonly<{ readonly children?: React.ReactNode }>) {
+ return ;
+}
+
+export function MarkdownOrderedList({ children }: Readonly<{ readonly children?: React.ReactNode }>) {
+ return {children}
;
+}
+
+export function MarkdownListItem({ children }: Readonly<{ readonly children?: React.ReactNode }>) {
+ return {children};
+}
+
+export function MarkdownLink({ href, children }: Readonly<{ readonly href?: string; readonly children?: React.ReactNode }>) {
+ return (
+
+ {children}
+
+ );
+}
+
+export const markdownComponents = {
+ code: MarkdownCode,
+ p: MarkdownParagraph,
+ ul: MarkdownUnorderedList,
+ ol: MarkdownOrderedList,
+ li: MarkdownListItem,
+ a: MarkdownLink,
+};
+
+export function AssistantMessageContent() {
+ return ;
+}
+
+export const assistantMessageComponents = {
+ Text: AssistantMessageContent,
+};
+
+export function AssistantMessage() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export function Thread() {
+ return (
+
+
+ s.thread.isEmpty}>
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/app/components/AIAssistant/index.tsx b/app/components/AIAssistant/index.tsx
new file mode 100644
index 0000000..72596b5
--- /dev/null
+++ b/app/components/AIAssistant/index.tsx
@@ -0,0 +1,31 @@
+"use client";
+
+import { useChatRuntime, AssistantChatTransport } from "@assistant-ui/react-ai-sdk";
+import { AssistantRuntimeProvider, AuiConfig, Tools } from "@assistant-ui/react";
+import { AssistantTrigger } from "@/components/AIAssistant/AssistantTrigger";
+import { assistantToolkit } from "@/components/AIAssistant/toolkit";
+
+export interface AIAssistantProps {
+ readonly api?: string;
+}
+
+export function AIAssistant({ api = "/api/chat" }: Readonly) {
+ const runtime = useChatRuntime({
+ transport: new AssistantChatTransport({ api }),
+ });
+
+ const config = AuiConfig({
+ tools: Tools({ toolkit: assistantToolkit }),
+ });
+
+ return (
+
+
+
+ );
+}
+
+export { useAIAssistant, AIAssistantProvider, AssistantTrigger } from "@/components/AIAssistant/AssistantTrigger";
+export { Thread } from "@/components/AIAssistant/Thread";
+export { Composer } from "@/components/AIAssistant/Composer";
+export { assistantToolkit } from "@/components/AIAssistant/toolkit";
diff --git a/app/components/AIAssistant/toolkit.ts b/app/components/AIAssistant/toolkit.ts
new file mode 100644
index 0000000..2e6cc88
--- /dev/null
+++ b/app/components/AIAssistant/toolkit.ts
@@ -0,0 +1,23 @@
+import { defineToolkit } from "@assistant-ui/react";
+import { DocSearchTool } from "@/components/AIAssistant/tools/DocSearchTool";
+import { ThemeTool } from "@/components/AIAssistant/tools/ThemeTool";
+import { SystemInfoTool } from "@/components/AIAssistant/tools/SystemInfoTool";
+import { NavigatePageTool } from "@/components/AIAssistant/tools/NavigatePageTool";
+
+export const assistantToolkit = defineToolkit({
+ searchDocumentation: {
+ render: DocSearchTool,
+ },
+ setTheme: {
+ render: ThemeTool,
+ },
+ getSystemInfo: {
+ render: SystemInfoTool,
+ },
+ navigatePage: {
+ render: NavigatePageTool,
+ },
+});
+
+export default assistantToolkit;
+
diff --git a/app/components/AIAssistant/tools/DocSearchTool.tsx b/app/components/AIAssistant/tools/DocSearchTool.tsx
new file mode 100644
index 0000000..086d0d7
--- /dev/null
+++ b/app/components/AIAssistant/tools/DocSearchTool.tsx
@@ -0,0 +1,74 @@
+"use client";
+
+import type { ToolCallMessagePartComponent } from "@assistant-ui/react";
+import Link from "next/link";
+import { BookOpen, ArrowRight } from "lucide-react";
+import type { DocItem } from "@/lib/ai/tools";
+
+export type DocSearchArgs = {
+ query?: string;
+};
+
+export type DocSearchResult = {
+ query: string;
+ results: DocItem[];
+};
+
+export const DocSearchTool: ToolCallMessagePartComponent = ({
+ args,
+ result,
+ status,
+}) => {
+ if (status.type === "running") {
+ return (
+
+
+ Searching documentation for "{args?.query || "topics"}"...
+
+ );
+ }
+
+ if (!result?.results?.length) {
+ return (
+
+ No documentation matches found for "{args?.query}".
+
+ );
+ }
+
+ return (
+
+
+
+
+ Documentation Results ({result.results.length})
+
+ Query: {result.query}
+
+
+
+ {result.results.map((item) => (
+
+
+
+ {item.title}
+
+
+ {item.category}
+
+
+
{item.description}
+
+
+ ))}
+
+
+ );
+};
diff --git a/app/components/AIAssistant/tools/NavigatePageTool.tsx b/app/components/AIAssistant/tools/NavigatePageTool.tsx
new file mode 100644
index 0000000..4200122
--- /dev/null
+++ b/app/components/AIAssistant/tools/NavigatePageTool.tsx
@@ -0,0 +1,49 @@
+"use client";
+
+import { useEffect } from "react";
+import type { ToolCallMessagePartComponent } from "@assistant-ui/react";
+import { useRouter } from "next/navigation";
+import { Navigation, ArrowRight } from "lucide-react";
+import Link from "next/link";
+
+export type NavigatePageArgs = {
+ route?: string;
+};
+
+export type NavigatePageResult = {
+ success: boolean;
+ route: string;
+ message: string;
+};
+
+export const NavigatePageTool: ToolCallMessagePartComponent = ({
+ args,
+ result,
+ status,
+}) => {
+ const router = useRouter();
+ const targetRoute = result?.route || args?.route;
+
+ useEffect(() => {
+ if (status.type === "complete" && targetRoute) {
+ router.push(targetRoute);
+ }
+ }, [status.type, targetRoute, router]);
+
+ return (
+
+
+
+ Navigating to {targetRoute}
+
+ {targetRoute && (
+
+ Go now
+
+ )}
+
+ );
+};
diff --git a/app/components/AIAssistant/tools/SystemInfoTool.tsx b/app/components/AIAssistant/tools/SystemInfoTool.tsx
new file mode 100644
index 0000000..e8118ca
--- /dev/null
+++ b/app/components/AIAssistant/tools/SystemInfoTool.tsx
@@ -0,0 +1,83 @@
+"use client";
+
+import type { ToolCallMessagePartComponent } from "@assistant-ui/react";
+import { Cpu, CheckCircle, Layers, Palette, Bot } from "lucide-react";
+
+export type SystemInfoArgs = Record;
+
+export type SystemInfoResult = {
+ name: string;
+ version: string;
+ framework: string;
+ runtime: string;
+ designSystem: string;
+ aiStack: string;
+ environment: string;
+ provider?: string;
+ status: string;
+};
+
+export const SystemInfoTool: ToolCallMessagePartComponent = ({
+ result,
+ status,
+}) => {
+ if (status.type === "running") {
+ return (
+
+
+ Querying system metrics and runtime info...
+
+ );
+ }
+
+ if (!result) return null;
+
+ return (
+
+
+
+
+ System Information
+
+
+
+ {result.status}
+
+
+
+
+
+
+
+
Framework
+
{result.framework}
+
+
+
+
+
+
+
Design System
+
{result.designSystem}
+
+
+
+
+
+
+
AI Stack
+
{result.aiStack}
+
+
+
+
+
+
+
Environment
+
{result.environment} ({result.provider || "mock"})
+
+
+
+
+ );
+};
diff --git a/app/components/AIAssistant/tools/ThemeTool.tsx b/app/components/AIAssistant/tools/ThemeTool.tsx
new file mode 100644
index 0000000..202b79c
--- /dev/null
+++ b/app/components/AIAssistant/tools/ThemeTool.tsx
@@ -0,0 +1,59 @@
+"use client";
+
+import { useEffect } from "react";
+import type { ToolCallMessagePartComponent } from "@assistant-ui/react";
+import { useTheme } from "next-themes";
+import { Sun, Moon, Laptop, CheckCircle2 } from "lucide-react";
+
+export type ThemeArgs = {
+ theme?: "light" | "dark" | "system";
+};
+
+export type ThemeResult = {
+ success: boolean;
+ theme: "light" | "dark" | "system";
+ message: string;
+};
+
+export const ThemeTool: ToolCallMessagePartComponent = ({
+ args,
+ result,
+ status,
+}) => {
+ const { setTheme } = useTheme();
+ const targetTheme = result?.theme || args?.theme;
+
+ useEffect(() => {
+ if (targetTheme) {
+ setTheme(targetTheme);
+ }
+ }, [targetTheme, setTheme]);
+
+ const getIcon = (theme?: string) => {
+ switch (theme) {
+ case "light":
+ return ;
+ case "dark":
+ return ;
+ default:
+ return ;
+ }
+ };
+
+ if (status.type === "running") {
+ return (
+
+ {getIcon(targetTheme)}
+ Switching theme to {targetTheme || "system"}...
+
+ );
+ }
+
+ return (
+
+ {getIcon(targetTheme)}
+ {targetTheme || "Theme"} applied
+
+
+ );
+};
diff --git a/app/components/Navbar/index.tsx b/app/components/Navbar/index.tsx
index f4b8086..8a58a62 100644
--- a/app/components/Navbar/index.tsx
+++ b/app/components/Navbar/index.tsx
@@ -1,8 +1,14 @@
+"use client";
+
import Link from "next/link";
import { ThemeToggle } from "@/components/ThemeToggle";
-import { ExternalLink } from "lucide-react";
+import { ExternalLink, Bot } from "lucide-react";
+import { useAIAssistant } from "@/components/AIAssistant";
+import { Button } from "@heroui/react";
export function Navbar() {
+ const { toggle } = useAIAssistant();
+
return (
@@ -15,7 +21,7 @@ export function Navbar() {
-
+
+
diff --git a/app/components/Providers/index.tsx b/app/components/Providers/index.tsx
index af3a92e..aedb606 100644
--- a/app/components/Providers/index.tsx
+++ b/app/components/Providers/index.tsx
@@ -4,6 +4,7 @@ import { RouterProvider } from "@heroui/react";
import { ThemeProvider as NextThemesProvider } from "next-themes";
import { useRouter } from "next/navigation";
import { SearchProvider } from "@/hooks/use-search-state";
+import { AIAssistantProvider } from "@/components/AIAssistant";
interface ProvidersProps {
children: React.ReactNode;
@@ -16,7 +17,9 @@ export function Providers({ children }: ProvidersProps) {
- {children}
+
+ {children}
+
diff --git a/app/hooks/use-speech-to-text.ts b/app/hooks/use-speech-to-text.ts
new file mode 100644
index 0000000..81d577f
--- /dev/null
+++ b/app/hooks/use-speech-to-text.ts
@@ -0,0 +1,156 @@
+"use client";
+
+import { useState, useEffect, useCallback, useRef } from "react";
+
+interface SpeechRecognitionEventLike {
+ resultIndex: number;
+ results: {
+ length: number;
+ [index: number]: {
+ [index: number]: {
+ transcript: string;
+ };
+ isFinal?: boolean;
+ };
+ };
+}
+
+interface SpeechRecognitionErrorEventLike {
+ error: string;
+}
+
+interface SpeechRecognitionLike {
+ continuous: boolean;
+ interimResults: boolean;
+ lang: string;
+ start: () => void;
+ stop: () => void;
+ abort: () => void;
+ onstart: (() => void) | null;
+ onresult: ((event: SpeechRecognitionEventLike) => void) | null;
+ onerror: ((event: SpeechRecognitionErrorEventLike) => void) | null;
+ onend: (() => void) | null;
+}
+
+type SpeechRecognitionConstructor = new () => SpeechRecognitionLike;
+
+interface SpeechRecognitionWindow {
+ SpeechRecognition?: SpeechRecognitionConstructor;
+ webkitSpeechRecognition?: SpeechRecognitionConstructor;
+}
+
+export interface UseSpeechToTextOptions {
+ lang?: string;
+ continuous?: boolean;
+ interimResults?: boolean;
+ onResult?: (transcript: string) => void;
+}
+
+export function useSpeechToText(options: UseSpeechToTextOptions = {}) {
+ const { lang = "en-US", continuous = false, interimResults = true, onResult } = options;
+
+ const [isListening, setIsListening] = useState(false);
+ const [transcript, setTranscript] = useState("");
+ const [isSupported, setIsSupported] = useState(false);
+ const [error, setError] = useState(null);
+
+ const recognitionRef = useRef(null);
+ const onResultRef = useRef(onResult);
+ onResultRef.current = onResult;
+
+ useEffect(() => {
+ if (typeof window !== "undefined") {
+ const speechWindow = window as unknown as SpeechRecognitionWindow;
+ const SpeechRecognitionClass =
+ speechWindow.SpeechRecognition || speechWindow.webkitSpeechRecognition;
+ if (SpeechRecognitionClass) {
+ setIsSupported(true);
+ const recognition: SpeechRecognitionLike = new SpeechRecognitionClass();
+ recognition.continuous = continuous;
+ recognition.interimResults = interimResults;
+ recognition.lang = lang;
+
+ recognition.onstart = () => {
+ setIsListening(true);
+ setError(null);
+ };
+
+ recognition.onresult = (event: SpeechRecognitionEventLike) => {
+ let currentTranscript = "";
+ for (let i = event.resultIndex; i < event.results.length; i++) {
+ currentTranscript += event.results[i][0].transcript;
+ }
+ setTranscript(currentTranscript);
+ if (onResultRef.current && currentTranscript) {
+ onResultRef.current(currentTranscript);
+ }
+ };
+
+ recognition.onerror = (event: SpeechRecognitionErrorEventLike) => {
+ setError(event.error);
+ setIsListening(false);
+ };
+
+ recognition.onend = () => {
+ setIsListening(false);
+ };
+
+ recognitionRef.current = recognition;
+ } else {
+ setIsSupported(false);
+ }
+ }
+
+ return () => {
+ if (recognitionRef.current) {
+ recognitionRef.current.abort();
+ }
+ };
+ }, [lang, continuous, interimResults]);
+
+ const startListening = useCallback(() => {
+ if (!recognitionRef.current) return;
+ try {
+ setTranscript("");
+ setError(null);
+ recognitionRef.current.start();
+ setIsListening(true);
+ } catch (err) {
+ setError(err instanceof Error ? err.message : "Failed to start speech recognition");
+ }
+ }, []);
+
+ const stopListening = useCallback(() => {
+ if (!recognitionRef.current) return;
+ try {
+ recognitionRef.current.stop();
+ setIsListening(false);
+ } catch {
+ // Ignore stop errors
+ }
+ }, []);
+
+ const toggleListening = useCallback(() => {
+ if (isListening) {
+ stopListening();
+ } else {
+ startListening();
+ }
+ }, [isListening, startListening, stopListening]);
+
+ const resetTranscript = useCallback(() => {
+ setTranscript("");
+ setError(null);
+ }, []);
+
+ return {
+ isListening,
+ transcript,
+ isSupported,
+ error,
+ startListening,
+ stopListening,
+ toggleListening,
+ resetTranscript,
+ };
+}
diff --git a/app/layout.tsx b/app/layout.tsx
index 64cc256..514896c 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -4,8 +4,9 @@ import { Analytics } from "@vercel/analytics/next";
import { Providers } from "@/components/Providers";
import { Navbar } from "@/components/Navbar";
import { Footer } from "@/components/Footer";
+import { AIAssistant } from "@/components/AIAssistant";
import { SpeedInsights } from "@vercel/speed-insights/next";
-import "./globals.css";
+import "@/globals.css";
const geistSans = Geist({
variable: "--font-geist-sans",
@@ -45,6 +46,7 @@ export default function RootLayout({ children }: RootLayoutProps) {
{children}
+
diff --git a/app/lib/ai/config.ts b/app/lib/ai/config.ts
new file mode 100644
index 0000000..689c2b6
--- /dev/null
+++ b/app/lib/ai/config.ts
@@ -0,0 +1,65 @@
+import type { LanguageModel } from "ai";
+import { createGoogleGenerativeAI } from "@ai-sdk/google";
+import { createOpenAI } from "@ai-sdk/openai";
+import { createAnthropic } from "@ai-sdk/anthropic";
+import { env } from "@/lib/env";
+import { mockModel } from "@/lib/ai/mock-provider";
+
+export type AIProvider = "mock" | "google" | "openai" | "anthropic" | "custom" | (string & {});
+
+export interface ModelResolutionOptions {
+ provider?: AIProvider;
+ model?: string;
+ apiKey?: string;
+ baseURL?: string;
+}
+
+export function getModel(options: ModelResolutionOptions = {}): LanguageModel {
+ const provider = options.provider || env.AI_PROVIDER || "mock";
+ const modelName = options.model || env.AI_MODEL;
+ const apiKey = options.apiKey;
+ const baseURL = options.baseURL || env.AI_BASE_URL;
+
+ switch (provider) {
+ case "google": {
+ const key = apiKey || env.GOOGLE_GENERATIVE_AI_API_KEY;
+ if (!key) return mockModel;
+ const google = createGoogleGenerativeAI({ apiKey: key });
+ return google(modelName || "gemini-2.5-flash");
+ }
+
+ case "openai": {
+ const key = apiKey || env.OPENAI_API_KEY;
+ if (!key) return mockModel;
+ const openai = createOpenAI({
+ apiKey: key,
+ baseURL: baseURL || undefined,
+ });
+ return openai(modelName || "gpt-4o-mini");
+ }
+
+ case "anthropic": {
+ const key = apiKey || env.ANTHROPIC_API_KEY;
+ if (!key) return mockModel;
+ const anthropic = createAnthropic({
+ apiKey: key,
+ baseURL: baseURL || undefined,
+ });
+ return anthropic(modelName || "claude-3-7-sonnet-20250219");
+ }
+
+ case "custom": {
+ const customOpenAI = createOpenAI({
+ apiKey: apiKey || env.OPENAI_API_KEY || "custom",
+ baseURL: baseURL || "http://localhost:11434/v1",
+ });
+ return customOpenAI(modelName || "llama3.2");
+ }
+
+ case "mock":
+ default:
+ return mockModel;
+ }
+}
+
+export const defaultModel = getModel();
diff --git a/app/lib/ai/env.ts b/app/lib/ai/env.ts
new file mode 100644
index 0000000..5fcfc21
--- /dev/null
+++ b/app/lib/ai/env.ts
@@ -0,0 +1,23 @@
+import { z } from "zod";
+
+export const aiEnvSchema = z.object({
+ AI_PROVIDER: z
+ .enum(["mock", "google", "openai", "anthropic", "custom"])
+ .default("mock"),
+ AI_MODEL: z.string().optional(),
+ GOOGLE_GENERATIVE_AI_API_KEY: z.string().optional(),
+ OPENAI_API_KEY: z.string().optional(),
+ ANTHROPIC_API_KEY: z.string().optional(),
+ AI_BASE_URL: z.url().optional().or(z.literal("")),
+});
+
+export const aiEnv = aiEnvSchema.parse({
+ AI_PROVIDER: process.env.AI_PROVIDER,
+ AI_MODEL: process.env.AI_MODEL,
+ GOOGLE_GENERATIVE_AI_API_KEY: process.env.GOOGLE_GENERATIVE_AI_API_KEY,
+ OPENAI_API_KEY: process.env.OPENAI_API_KEY,
+ ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY,
+ AI_BASE_URL: process.env.AI_BASE_URL,
+});
+
+export type AIEnv = z.infer;
diff --git a/app/lib/ai/mock-provider.ts b/app/lib/ai/mock-provider.ts
new file mode 100644
index 0000000..0734025
--- /dev/null
+++ b/app/lib/ai/mock-provider.ts
@@ -0,0 +1,172 @@
+import { MockLanguageModelV3 } from "ai/test";
+import type { LanguageModel } from "ai";
+
+export interface MockModelOptions {
+ customResponses?: Record;
+ simulateTools?: boolean;
+}
+
+function extractPromptText(prompt: unknown): string {
+ if (typeof prompt === "string") return prompt.toLowerCase();
+ if (Array.isArray(prompt)) {
+ return prompt
+ .map((msg) => {
+ if (typeof msg === "string") return msg;
+ if (typeof msg === "object" && msg !== null) {
+ if ("content" in msg) {
+ const content = (msg as { content: unknown }).content;
+ if (typeof content === "string") return content;
+ if (Array.isArray(content)) {
+ return content
+ .map((part) => (typeof part === "object" && part && "text" in part ? String((part as { text: unknown }).text) : ""))
+ .join(" ");
+ }
+ }
+ }
+ return "";
+ })
+ .join(" ")
+ .toLowerCase();
+ }
+ return "";
+}
+
+export function createMockModel(options: MockModelOptions = {}): LanguageModel {
+ const { simulateTools = true } = options;
+
+ return new MockLanguageModelV3({
+ modelId: "cur8d-mock-model",
+ doGenerate: async ({ prompt }) => {
+ const text = extractPromptText(prompt);
+ const content = `Mock AI response for query: "${text}". cur8d is configured with Next.js 16, HeroUI v3, Tailwind CSS v4, and assistant-ui.`;
+ return {
+ content: [{ type: "text", text: content }],
+ finishReason: { unified: "stop", raw: "stop" },
+ usage: {
+ inputTokens: { total: 10, noCache: 10, cacheRead: 0, cacheWrite: 0 },
+ outputTokens: { total: 25, text: 25, reasoning: 0 },
+ },
+ warnings: [],
+ rawCall: { rawPrompt: prompt, rawSettings: {} },
+ };
+ },
+ doStream: async ({ prompt }) => {
+ const text = extractPromptText(prompt);
+
+ // Check for tool triggers if simulateTools is true
+ if (simulateTools && (text.includes("theme") || text.includes("dark") || text.includes("light"))) {
+ const theme = text.includes("light") ? "light" : "dark";
+ return {
+ stream: new ReadableStream({
+ start(controller) {
+ controller.enqueue({
+ type: "tool-call",
+ toolCallId: "call_theme_1",
+ toolName: "setTheme",
+ input: JSON.stringify({ theme }),
+ });
+ controller.enqueue({
+ type: "finish",
+ finishReason: { unified: "tool-calls", raw: "tool-calls" },
+ usage: {
+ inputTokens: { total: 12, noCache: 12, cacheRead: 0, cacheWrite: 0 },
+ outputTokens: { total: 18, text: 18, reasoning: 0 },
+ },
+ });
+ controller.close();
+ },
+ }),
+ rawCall: { rawPrompt: prompt, rawSettings: {} },
+ };
+ }
+
+ if (simulateTools && (text.includes("system") || text.includes("info") || text.includes("stack"))) {
+ return {
+ stream: new ReadableStream({
+ start(controller) {
+ controller.enqueue({
+ type: "tool-call",
+ toolCallId: "call_sysinfo_1",
+ toolName: "getSystemInfo",
+ input: JSON.stringify({}),
+ });
+ controller.enqueue({
+ type: "finish",
+ finishReason: { unified: "tool-calls", raw: "tool-calls" },
+ usage: {
+ inputTokens: { total: 10, noCache: 10, cacheRead: 0, cacheWrite: 0 },
+ outputTokens: { total: 15, text: 15, reasoning: 0 },
+ },
+ });
+ controller.close();
+ },
+ }),
+ rawCall: { rawPrompt: prompt, rawSettings: {} },
+ };
+ }
+
+ if (simulateTools && (text.includes("search") || text.includes("doc") || text.includes("guide"))) {
+ return {
+ stream: new ReadableStream({
+ start(controller) {
+ controller.enqueue({
+ type: "tool-call",
+ toolCallId: "call_docs_1",
+ toolName: "searchDocumentation",
+ input: JSON.stringify({ query: text.includes("search") ? text.replace("search", "").trim() : "overview" }),
+ });
+ controller.enqueue({
+ type: "finish",
+ finishReason: { unified: "tool-calls", raw: "tool-calls" },
+ usage: {
+ inputTokens: { total: 15, noCache: 15, cacheRead: 0, cacheWrite: 0 },
+ outputTokens: { total: 20, text: 20, reasoning: 0 },
+ },
+ });
+ controller.close();
+ },
+ }),
+ rawCall: { rawPrompt: prompt, rawSettings: {} },
+ };
+ }
+
+ // Default mock streaming response
+ const chunks = [
+ "👋 **Welcome to cur8d AI Assistant!**\n\n",
+ "I am your built-in developer copilot running in **Zero-Config Mock Mode**. Here is what makes `cur8d` special:\n\n",
+ "- ⚡ **Next.js 16 & React 19**: App Router, Server Components & Turbopack\n",
+ "- 🎨 **HeroUI v3 & Tailwind CSS v4**: Accessible compound components and theme variables\n",
+ "- 🤖 **assistant-ui & Vercel AI SDK**: Production-ready streaming chat and generative UI\n",
+ "- 🧪 **Vitest & Playwright**: Unit tests, $\\ge 80\\%$ coverage, and accessibility audits\n\n",
+ "```bash\n# Verify your project anytime\nmise run verify\n```\n\n",
+ "Ask me anything about components, routing, or tools!",
+ ];
+
+ return {
+ stream: new ReadableStream({
+ start(controller) {
+ for (let i = 0; i < chunks.length; i++) {
+ controller.enqueue({
+ type: "text-delta",
+ id: `chunk_${i}`,
+ delta: chunks[i],
+ });
+ }
+ controller.enqueue({
+ type: "finish",
+ finishReason: { unified: "stop", raw: "stop" },
+ usage: {
+ inputTokens: { total: 20, noCache: 20, cacheRead: 0, cacheWrite: 0 },
+ outputTokens: { total: chunks.join("").length, text: chunks.join("").length, reasoning: 0 },
+ },
+ });
+ controller.close();
+ },
+ }),
+ rawCall: { rawPrompt: prompt, rawSettings: {} },
+ };
+ },
+ });
+}
+
+export const mockModel = createMockModel();
diff --git a/app/lib/ai/system-prompt.ts b/app/lib/ai/system-prompt.ts
new file mode 100644
index 0000000..00f2967
--- /dev/null
+++ b/app/lib/ai/system-prompt.ts
@@ -0,0 +1,21 @@
+export const SYSTEM_PROMPT = `You are the AI developer assistant for cur8d, a production-ready Next.js starter optimized for performance, accessibility, and type safety.
+
+## Technical Context & Guidelines:
+- **Framework**: Next.js 16 (App Router, Turbopack, React 19 Server Components by default; "use client" only for interactive leaf components).
+- **Design System**: HeroUI v3 with compound components using dot notation (e.g. , , ).
+- **Styling**: Tailwind CSS v4 with CSS-first variables and next-themes dark mode support.
+- **Icons**: Named imports from 'lucide-react' with size-* or h-* w-* utility classes.
+- **Toolchain**: Managed with mise (e.g. 'mise run verify', 'mise run dev', 'mise run test', 'mise run build').
+- **Testing**: Vitest for unit tests (>= 80% coverage required) and Playwright for E2E and axe-core accessibility audits.
+- **Data & Utilities**: Zod schemas in app/lib/env.ts, centralized error reporting in app/lib/error-reporting.ts.
+
+## Available Tools:
+1. \`searchDocumentation\`: Use whenever the user asks about documentation, guides, setup steps, or API references.
+2. \`setTheme\`: Use when the user requests switching theme (light, dark, or system).
+3. \`getSystemInfo\`: Use when the user asks about current project stack, versions, or health status.
+4. \`navigatePage\`: Use when the user asks to navigate to a specific page or route.
+
+## Response Style:
+- Be concise, friendly, precise, and practical.
+- Use GitHub-flavored Markdown with syntax-highlighted code blocks where helpful.
+- Suggest mise commands (e.g., 'mise run verify') when giving build/test guidance.`;
diff --git a/app/lib/ai/tools.ts b/app/lib/ai/tools.ts
new file mode 100644
index 0000000..e9d5ec5
--- /dev/null
+++ b/app/lib/ai/tools.ts
@@ -0,0 +1,141 @@
+import { tool } from "ai";
+import { z } from "zod";
+import { env } from "@/lib/env";
+
+export interface DocItem {
+ title: string;
+ path: string;
+ description: string;
+ category: string;
+}
+
+export interface SystemInfo {
+ name: string;
+ version: string;
+ framework: string;
+ runtime: string;
+ designSystem: string;
+ aiStack: string;
+ environment: string;
+ provider: string;
+ status: string;
+}
+
+export const DOCS_CATALOG: DocItem[] = [
+ {
+ title: "Project Overview",
+ path: "/docs",
+ description: "Learn about the cur8d starter architecture, philosophy, and features.",
+ category: "Getting Started",
+ },
+ {
+ title: "Environment Variables",
+ path: "/docs/getting-started/environment-variables",
+ description: "Manage and validate environment variables with Zod.",
+ category: "Getting Started",
+ },
+ {
+ title: "AI Assistant",
+ path: "/docs/features/ai-assistant",
+ description: "assistant-ui and Vercel AI SDK integration with zero-config mock mode.",
+ category: "Features",
+ },
+ {
+ title: "Theme & Dark Mode",
+ path: "/docs/features/theme",
+ description: "next-themes and Tailwind CSS v4 CSS variables integration.",
+ category: "Features",
+ },
+ {
+ title: "Testing Guide",
+ path: "/docs/reference/testing",
+ description: "Vitest unit tests (80% coverage) and Playwright a11y testing.",
+ category: "Reference",
+ },
+ {
+ title: "CLI & Mise Commands",
+ path: "/docs/reference/commands",
+ description: "Toolchain commands for dev, verify, test, lint, and build.",
+ category: "Reference",
+ },
+ {
+ title: "Deployment",
+ path: "/docs/deployment",
+ description: "Deploy cur8d to Vercel, Render, or Firebase Hosting.",
+ category: "Deployment",
+ },
+];
+
+export const searchDocumentation = tool({
+ description: "Search documentation and feature guides for cur8d.",
+ inputSchema: z.object({
+ query: z.string().describe("Search keywords or topic to find"),
+ }),
+ execute: async ({ query }: { query: string }) => {
+ const q = query.toLowerCase();
+ const results = DOCS_CATALOG.filter(
+ (item) =>
+ item.title.toLowerCase().includes(q) ||
+ item.description.toLowerCase().includes(q) ||
+ item.category.toLowerCase().includes(q)
+ );
+
+ return {
+ query,
+ results: results.length > 0 ? results : DOCS_CATALOG.slice(0, 3),
+ };
+ },
+});
+
+export const setTheme = tool({
+ description: "Set the active color theme of the application (light, dark, or system).",
+ inputSchema: z.object({
+ theme: z.enum(["light", "dark", "system"]).describe("The desired theme"),
+ }),
+ execute: async ({ theme }: { theme: "light" | "dark" | "system" }) => {
+ return {
+ success: true,
+ theme,
+ message: `Theme has been requested to be set to '${theme}'.`,
+ };
+ },
+});
+
+export const getSystemInfo = tool({
+ description: "Get technical specifications and version metadata for the cur8d stack.",
+ inputSchema: z.object({}),
+ execute: async () => {
+ return {
+ name: "cur8d",
+ version: "0.1.0",
+ framework: "Next.js 16 (App Router / Turbopack)",
+ runtime: "React 19 Server Components",
+ designSystem: "HeroUI v3 + Tailwind CSS v4",
+ aiStack: "assistant-ui + Vercel AI SDK",
+ environment: env.NODE_ENV,
+ provider: env.AI_PROVIDER,
+ status: "operational",
+ };
+ },
+});
+
+export const navigatePage = tool({
+ description: "Navigate to a specific page or route within the cur8d application.",
+ inputSchema: z.object({
+ route: z.string().describe("Target route path (e.g., '/', '/docs', '/docs/features/ai-assistant')"),
+ }),
+ execute: async ({ route }: { route: string }) => {
+ return {
+ success: true,
+ route,
+ message: `Navigating to ${route}`,
+ };
+ },
+});
+
+export const aiTools = {
+ searchDocumentation,
+ setTheme,
+ getSystemInfo,
+ navigatePage,
+};
diff --git a/app/lib/env.ts b/app/lib/env.ts
index 857682a..d761c64 100644
--- a/app/lib/env.ts
+++ b/app/lib/env.ts
@@ -1,6 +1,7 @@
import { z } from "zod";
+import { aiEnvSchema } from "@/lib/ai/env";
-const envSchema = z.object({
+const baseEnvSchema = z.object({
NODE_ENV: z.enum(["development", "production", "test"]).default("development"),
BLOB_READ_WRITE_TOKEN: z
.string()
@@ -9,9 +10,17 @@ const envSchema = z.object({
.optional(),
});
+const envSchema = baseEnvSchema.extend(aiEnvSchema.shape);
+
export const env = envSchema.parse({
NODE_ENV: process.env.NODE_ENV,
BLOB_READ_WRITE_TOKEN: process.env.BLOB_READ_WRITE_TOKEN,
+ AI_PROVIDER: process.env.AI_PROVIDER,
+ AI_MODEL: process.env.AI_MODEL,
+ GOOGLE_GENERATIVE_AI_API_KEY: process.env.GOOGLE_GENERATIVE_AI_API_KEY,
+ OPENAI_API_KEY: process.env.OPENAI_API_KEY,
+ ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY,
+ AI_BASE_URL: process.env.AI_BASE_URL,
});
export type Env = z.infer;
diff --git a/docs/app/[[...mdxPath]]/page.tsx b/docs/app/[[...mdxPath]]/page.tsx
index 2244f7c..bab62cb 100644
--- a/docs/app/[[...mdxPath]]/page.tsx
+++ b/docs/app/[[...mdxPath]]/page.tsx
@@ -1,5 +1,5 @@
import { generateStaticParamsFor, importPage } from 'nextra/pages'
-import { useMDXComponents as getMDXComponents } from '../../mdx-components'
+import { useMDXComponents as getMDXComponents } from '@docs/mdx-components'
export const generateStaticParams = generateStaticParamsFor('mdxPath')
diff --git a/docs/content/_meta.js b/docs/content/_meta.js
index 449778a..34961d6 100644
--- a/docs/content/_meta.js
+++ b/docs/content/_meta.js
@@ -1,6 +1,7 @@
export default {
"index": "Introduction",
"getting-started": "Getting Started",
+ "features": "Features",
"guides": "Guides",
"reference": "Reference",
"deployment": "Deployment",
diff --git a/docs/content/features/_meta.js b/docs/content/features/_meta.js
new file mode 100644
index 0000000..d553dd1
--- /dev/null
+++ b/docs/content/features/_meta.js
@@ -0,0 +1,3 @@
+export default {
+ "ai-assistant": "AI Assistant"
+}
diff --git a/docs/content/features/ai-assistant.mdx b/docs/content/features/ai-assistant.mdx
new file mode 100644
index 0000000..7b28f84
--- /dev/null
+++ b/docs/content/features/ai-assistant.mdx
@@ -0,0 +1,138 @@
+# AI Assistant
+
+cur8d includes a production-ready, accessible, and extensible **AI Assistant** interface powered by **[`assistant-ui`](https://www.assistant-ui.com/)** (`@assistant-ui/react`) and the **[Vercel AI SDK](https://sdk.vercel.ai/)** (`ai`).
+
+---
+
+## Highlights
+
+- ⚡ **Zero-Config Default**: Works instantly in development and CI/CD without requiring third-party API keys using our built-in realistic mock provider.
+- 🎨 **HeroUI v3 & Tailwind CSS v4**: Beautiful, accessible slide-over drawer shell, floating trigger badge, and compound component styling.
+- 🛠️ **Generative UI & Tool Calling**: Interactive tool cards for searching documentation, switching theme, and inspecting system metrics.
+- 🎙️ **Voice Input**: Speech-to-text dictation powered by the Web Speech API with microphone indicator.
+- ⌨️ **Keyboard Accessibility**: Full keyboard support with `⌘J` / `Ctrl+J` toggle, `Escape` dismissal, and ARIA attributes.
+- 🔄 **Multi-Provider Switching**: Dynamic model resolution for Google Gemini, OpenAI GPT-4o, Anthropic Claude, and local LLMs (Ollama).
+
+---
+
+## Architecture
+
+```
+User (⌘J / Click Trigger)
+ │
+ ▼
+AssistantTrigger (Floating Action Button & Modal Shell)
+ │
+ ▼
+Thread & Composer (@assistant-ui/react)
+ │
+ ▼
+useChatRuntime (@assistant-ui/react-ai-sdk)
+ │ (POST /api/chat - SSE Stream)
+ ▼
+App Router Route Handler (streamText())
+ ┌─────┴─────────────────────────┐
+ │ │
+ ▼ ▼
+Provider Resolver Tool Registry
+ - Mock (Zero-Config) - searchDocumentation
+ - Google Gemini 2.5 - setTheme
+ - OpenAI GPT-4o - getSystemInfo
+ - Anthropic Claude 3.7 - navigatePage
+ - Local Ollama / Custom
+```
+
+---
+
+## Environment Variables
+
+Configure your preferred AI provider in `.env`:
+
+```bash
+# Provider: "mock" (default) | "google" | "openai" | "anthropic" | "custom"
+AI_PROVIDER=mock
+
+# Model name override (optional)
+AI_MODEL=
+
+# API Keys (required when provider is not "mock")
+GOOGLE_GENERATIVE_AI_API_KEY=
+OPENAI_API_KEY=
+ANTHROPIC_API_KEY=
+
+# Base URL for custom or local OpenAI-compatible endpoints (e.g., Ollama)
+AI_BASE_URL=http://localhost:11434/v1
+```
+
+---
+
+## Generative UI Tools
+
+The AI assistant can invoke client and server tools to render rich generative cards:
+
+### 1. `searchDocumentation`
+Searches project guides and docs catalog, rendering interactive cards with direct navigation links.
+
+### 2. `setTheme`
+Changes active theme (`light`, `dark`, `system`) on the client using `next-themes` and displays confirmation feedback.
+
+### 3. `getSystemInfo`
+Queries framework versions (Next.js 16, React 19, HeroUI v3, Tailwind CSS v4) and reports environment health.
+
+### 4. `navigatePage`
+Navigates the user to a target URL or route within the application.
+
+---
+
+## Adding Custom Tools
+
+To add a new server tool and generative UI card:
+
+1. **Define the Server Tool** in `app/lib/ai/tools.ts`:
+ ```typescript
+ import { tool } from "ai";
+ import { z } from "zod";
+
+ export const myCustomTool = tool({
+ description: "Description of what this tool does",
+ inputSchema: z.object({
+ query: z.string(),
+ }),
+ execute: async ({ query }) => {
+ return { success: true, data: query };
+ },
+ });
+ ```
+
+2. **Create the Generative UI Component** in `app/components/AIAssistant/tools/`:
+ ```typescript
+ "use client";
+ import type { ToolCallMessagePartComponent } from "@assistant-ui/react";
+
+ export interface MyCustomToolArgs {
+ query: string;
+ }
+
+ export interface MyCustomToolResult {
+ success: boolean;
+ data: string;
+ }
+
+ export const MyCustomToolUI: ToolCallMessagePartComponent = ({ result, status }) => {
+ if (status.type === "running") return Running...
;
+ return Result: {JSON.stringify(result)}
;
+ };
+ ```
+
+3. **Register the Tool in Toolkit** in `app/components/AIAssistant/toolkit.ts`:
+ ```typescript
+ import { defineToolkit } from "@assistant-ui/react";
+ import { MyCustomToolUI } from "@/components/AIAssistant/tools/MyCustomTool";
+
+ export const assistantToolkit = defineToolkit({
+ // ...other tools
+ myCustomTool: {
+ render: MyCustomToolUI,
+ },
+ });
+ ```
diff --git a/docs/tsconfig.json b/docs/tsconfig.json
index c1a1454..20ee82d 100644
--- a/docs/tsconfig.json
+++ b/docs/tsconfig.json
@@ -21,7 +21,12 @@
{
"name": "next"
}
- ]
+ ],
+ "paths": {
+ "@docs/*": [
+ "./*"
+ ]
+ }
},
"include": [
"next-env.d.ts",
diff --git a/package.json b/package.json
index d36f3cf..12159c4 100644
--- a/package.json
+++ b/package.json
@@ -21,6 +21,15 @@
"deploy:render": "curl -f -X POST $RENDER_DEPLOY_HOOK"
},
"dependencies": {
+ "@ai-sdk/anthropic": "^4.0.41",
+ "@ai-sdk/google": "^4.0.50",
+ "@ai-sdk/openai": "^4.0.46",
+ "@ai-sdk/provider": "^4.0.7",
+ "@ai-sdk/react": "^4.0.80",
+ "@assistant-ui/react": "^0.15.16",
+ "@assistant-ui/react-ai-sdk": "^1.4.7",
+ "@assistant-ui/react-markdown": "^0.14.12",
+ "@assistant-ui/react-syntax-highlighter": "^0.14.5",
"@heroui/react": "3.2.4",
"@heroui/styles": "^3.2.4",
"@icons-pack/react-simple-icons": "^13.15.1",
@@ -28,6 +37,7 @@
"@vercel/analytics": "^2.0.1",
"@vercel/blob": "2.8.0",
"@vercel/speed-insights": "^2.0.0",
+ "ai": "^7.0.77",
"cmdk": "1.1.1",
"framer-motion": "13.1.1",
"lucide-react": "1.33.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 7160566..56f8779 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -27,6 +27,33 @@ importers:
.:
dependencies:
+ '@ai-sdk/anthropic':
+ specifier: ^4.0.41
+ version: 4.0.41(zod@4.4.3)
+ '@ai-sdk/google':
+ specifier: ^4.0.50
+ version: 4.0.50(zod@4.4.3)
+ '@ai-sdk/openai':
+ specifier: ^4.0.46
+ version: 4.0.46(zod@4.4.3)
+ '@ai-sdk/provider':
+ specifier: ^4.0.7
+ version: 4.0.7
+ '@ai-sdk/react':
+ specifier: ^4.0.80
+ version: 4.0.80(react@19.2.8)(zod@4.4.3)
+ '@assistant-ui/react':
+ specifier: ^0.15.16
+ version: 0.15.16(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(use-sync-external-store@1.6.0(react@19.2.8))
+ '@assistant-ui/react-ai-sdk':
+ specifier: ^1.4.7
+ version: 1.4.7(@types/react@19.2.18)(react@19.2.8)(zod@4.4.3)(zustand@5.0.15(@types/react@19.2.18)(react@19.2.8)(use-sync-external-store@1.6.0(react@19.2.8)))
+ '@assistant-ui/react-markdown':
+ specifier: ^0.14.12
+ version: 0.14.12(@assistant-ui/react@0.15.16(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(use-sync-external-store@1.6.0(react@19.2.8)))(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(supports-color@7.2.0)
+ '@assistant-ui/react-syntax-highlighter':
+ specifier: ^0.14.5
+ version: 0.14.5(@assistant-ui/react-markdown@0.14.12(@assistant-ui/react@0.15.16(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(use-sync-external-store@1.6.0(react@19.2.8)))(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(supports-color@7.2.0))(@assistant-ui/react@0.15.16(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(use-sync-external-store@1.6.0(react@19.2.8)))(@types/react@19.2.18)(react-syntax-highlighter@16.1.1(react@19.2.8))(react@19.2.8)
'@heroui/react':
specifier: 3.2.4
version: 3.2.4(@react-aria/i18n@3.13.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(@react-aria/ssr@3.10.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(@react-aria/utils@3.34.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(@react-spectrum/provider@3.11.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-aria-components@1.20.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react-aria@3.51.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(tailwindcss@4.3.3)
@@ -48,6 +75,9 @@ importers:
'@vercel/speed-insights':
specifier: ^2.0.0
version: 2.0.0(next@16.3.2(@babel/core@7.29.7(supports-color@7.2.0))(@opentelemetry/api@1.9.1)(@playwright/test@1.62.1)(@types/node@26.2.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react@19.2.8)
+ ai:
+ specifier: ^7.0.77
+ version: 7.0.77(zod@4.4.3)
cmdk:
specifier: 1.1.1
version: 1.1.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
@@ -239,6 +269,52 @@ packages:
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
+ '@ai-sdk/anthropic@4.0.41':
+ resolution: {integrity: sha512-So62as8fexWMmXMIxvrWVaDLjLRe6EG2+5mmRc1KCPBoyyyFx6zbTcCvxJkViSUHhsGMBDryAm+mfs91n7Jl8g==}
+ engines: {node: '>=22'}
+ peerDependencies:
+ zod: ^3.25.76 || ^4.1.8
+
+ '@ai-sdk/gateway@4.0.62':
+ resolution: {integrity: sha512-zR3pustGWhw5eUZHG+fJZx/V/PBe+LxdDpc5hDFWxozG/3MB/+eY62jn+YiR+9uOH+Hx63e5zJoeKLfZfPktWQ==}
+ engines: {node: '>=22'}
+ peerDependencies:
+ zod: ^3.25.76 || ^4.1.8
+
+ '@ai-sdk/google@4.0.50':
+ resolution: {integrity: sha512-n7aMmqw6ZGVNBDAiv89fZkuZp3ahfzwZTXsdW27WugXdFSIuLTlYBZ+FlKGRdk1pRMPrdIGT2GPL2Ra0Gc2Tkg==}
+ engines: {node: '>=22'}
+ peerDependencies:
+ zod: ^3.25.76 || ^4.1.8
+
+ '@ai-sdk/mcp@2.0.36':
+ resolution: {integrity: sha512-0RB0THm8EcTjvgNYifsXV21O0IozTx2/uJmtIjzY4myET1iVsvUh07xVfwSRY7keBkJhmK2kaF99qFlrWZDLXQ==}
+ engines: {node: '>=22'}
+ peerDependencies:
+ zod: ^3.25.76 || ^4.1.8
+
+ '@ai-sdk/openai@4.0.46':
+ resolution: {integrity: sha512-J53QUkZGOV9YIYf9caVjejdPZfVZ3V2xD5KyTAmMWFiMDkC/IcifEQHJferpGFaQkfmSehPGVPCLE4g8+VdkTA==}
+ engines: {node: '>=22'}
+ peerDependencies:
+ zod: ^3.25.76 || ^4.1.8
+
+ '@ai-sdk/provider-utils@5.0.29':
+ resolution: {integrity: sha512-7EIbwXiXKGa7EFk6tDZpuZBs6lxhEJpOuHeqrDb3Vd85uYdjwkdRuHnZDVDIIb2+QTSRmyph2NrXcbvuO/KAjQ==}
+ engines: {node: '>=22'}
+ peerDependencies:
+ zod: ^3.25.76 || ^4.1.8
+
+ '@ai-sdk/provider@4.0.7':
+ resolution: {integrity: sha512-6or44XprPzKbr8zkmzosowSE0pxkvJcoojBL+mCZvPUt3kvXp3XSNqeVun9golb1acEfSo6yaEBRT18h2VU+1Q==}
+ engines: {node: '>=22'}
+
+ '@ai-sdk/react@4.0.80':
+ resolution: {integrity: sha512-iZ/RODgwHUgeT7153wVSDCVc/Ct7gUvkauQAPcGZxgFgo1orAf6C964KwjpCwAgrQF324o3uo5QJQUvTTl8GLg==}
+ engines: {node: '>=22'}
+ peerDependencies:
+ react: ^18 || ~19.0.1 || ~19.1.2 || ^19.2.1
+
'@alloc/quick-lru@5.2.0':
resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
engines: {node: '>=10'}
@@ -260,6 +336,106 @@ packages:
resolution: {integrity: sha512-93Z1N+BQNXysodoicpOIyNh2drHfz/CTf9nnT0FEx72GJcIiwgydD7tGAr78j41LsYn3hlRn+LdGPuBLn1Bl8Q==}
engines: {node: ^22.13.0 || >=24.0.0}
+ '@assistant-ui/ai-sdk@0.0.2':
+ resolution: {integrity: sha512-4mjZwR/dRVDORtIrLfK+aD3Lel/TDPlDrEaDyTjnsPYAhohNHcE9LDW4wQxcLrLQPgz6QwEPQEGwREvk7yIatA==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^18 || ^19
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ react:
+ optional: true
+
+ '@assistant-ui/core@0.3.15':
+ resolution: {integrity: sha512-pCeVhMmzK4xFbahtRtvjlGXDxycsvl1plgiD0M5ZyABm+QxINGnBO5GEz1hqDHQVjKlCIJDNj15PlFSkuFnidA==}
+ peerDependencies:
+ '@assistant-ui/store': ^0.3.0
+ '@assistant-ui/tap': ^0.9.0
+ '@types/react': '*'
+ assistant-cloud: ^0.1.31
+ react: ^18 || ^19
+ zustand: ^5.0.11
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ assistant-cloud:
+ optional: true
+ react:
+ optional: true
+ zustand:
+ optional: true
+
+ '@assistant-ui/react-ai-sdk@1.4.7':
+ resolution: {integrity: sha512-fVeJ0TzvdHwSyRt9OHqPUJEHaqORmOxj5e552N2ie6GaMuMhklSU54bzLnlEHqP2m92Ee7dFj5WwEKKckawZXQ==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^18 || ^19
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@assistant-ui/react-markdown@0.14.12':
+ resolution: {integrity: sha512-g8uRpTpwk43qYzknPh7k1U0FysdJ4gySY88NiBLbCRWEGy/aHPy5B+6kaRttND3Rij3rQpGkP5NZOo/z+kMEEQ==}
+ peerDependencies:
+ '@assistant-ui/react': ^0.15.0
+ '@types/react': '*'
+ react: ^18 || ^19
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@assistant-ui/react-syntax-highlighter@0.14.5':
+ resolution: {integrity: sha512-5pc6KSeT65ArDxxlBuSwRdG1SvCPVm1rubKHbhtcr52UGvSQ4KwebKVYifURRywVXi8DvVrLye/a9AMCZAoN0g==}
+ peerDependencies:
+ '@assistant-ui/react': ^0.15.0
+ '@assistant-ui/react-markdown': ^0.14.8
+ '@types/react': '*'
+ '@types/react-syntax-highlighter': '*'
+ react: ^18 || ^19
+ react-syntax-highlighter: ^16.0.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-syntax-highlighter':
+ optional: true
+
+ '@assistant-ui/react@0.15.16':
+ resolution: {integrity: sha512-+2BO6npVEXdViNWTyH3XddVXHo7SOcXliM8btrXGzH6PHuTtn3CYL7DCQ5ZXVRHB9kYPBbbMPURVu93jg9qIwg==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^18 || ^19
+ react-dom: ^18 || ^19
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@assistant-ui/store@0.3.10':
+ resolution: {integrity: sha512-QSFgodFt/daEjyaY09WdahNewsGPtAeY+DkSb2LM2rPN6634h13R1vJQvCtyWK5YsI5WXgzgbsctzgpIQLU0qw==}
+ peerDependencies:
+ '@assistant-ui/tap': ^0.9.12
+ '@types/react': '*'
+ react: ^18 || ^19
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ react:
+ optional: true
+
+ '@assistant-ui/tap@0.9.14':
+ resolution: {integrity: sha512-L/ZyXLQb51/d+/5xlXaP1197PF94EO/Ji+/CBWTjuEbsU1+8dzXCHVNO9GCFQvyG02OiOVitfbRksipMVzdrnA==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^18 || ^19
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ react:
+ optional: true
+
'@axe-core/playwright@4.13.0':
resolution: {integrity: sha512-6YLx+kxXu5GJceG4ozFg+33a2EMTdjYwWGloJ3sb9Kta5pp+ZNS53uxGVog5JetIY8s++P5UrtX+cri+u0VAVg==}
peerDependencies:
@@ -1629,11 +1805,14 @@ packages:
'@protobufjs/utf8@1.1.2':
resolution: {integrity: sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug==}
+ '@radix-ui/number@1.1.3':
+ resolution: {integrity: sha512-Road2bidD0uu/1BGDOWNdPI06g0lIRy6IF9GZcIrDK2KGItfor8IQwQa+yM2ERgHM1MmHxaxpTzk0/Jp42lNfA==}
+
'@radix-ui/primitive@1.1.7':
resolution: {integrity: sha512-rqWnm76nYT8HoNNqEjpgJ7Pw/DrBj5iBTrmEPo6HTX5+VJyBNOqTdv4g89G63HuR5g0AaENoAcH7Is5fF2kZ8Q==}
- '@radix-ui/react-avatar@1.1.11':
- resolution: {integrity: sha512-0Qk603AHGV28BOBO34p7IgD5m+V5Sg/YovfayABkoDDBM5d3NCx0Mp4gGrjzLGes1jV5eNOE1r3itqOR33VC6Q==}
+ '@radix-ui/react-accessible-icon@1.1.15':
+ resolution: {integrity: sha512-WTQwcAvQf5sOcuUyi90lKPbhwcvQ+j55cjrSmeaN+L2vKU3DooOvlKw2MDeiJ5IkV5N905KW0/fGojKOBhD11A==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
@@ -1645,44 +1824,60 @@ packages:
'@types/react-dom':
optional: true
- '@radix-ui/react-compose-refs@1.1.2':
- resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==}
+ '@radix-ui/react-accordion@1.2.20':
+ resolution: {integrity: sha512-jDhG9FvAEnlhnjrsINbNXcUa4G+L1KqSkJSunkbKEzFRcAb52jvM0PjPxPRvhe1HNc5F5yc0yzzWeeqlH4yBIg==}
peerDependencies:
'@types/react': '*'
+ '@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
+ '@types/react-dom':
+ optional: true
- '@radix-ui/react-compose-refs@1.1.5':
- resolution: {integrity: sha512-+48PbAAbq3didjJxa+OaWY2ZwgAKsNiRGyeHKszblZMQ+kcpd9pAaT11cMkGEie0vsOi3QdeTE6d5Fe3Gn61kA==}
+ '@radix-ui/react-alert-dialog@1.1.23':
+ resolution: {integrity: sha512-VAYOiQRqj3GPpYJE0I9J+X8Ip05cyVlNdKOFeiGS2Ou1HHGfpl0BxOyZm6nmVDyU+W+NF3/XLzmjHmVGydhwgA==}
peerDependencies:
'@types/react': '*'
+ '@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
+ '@types/react-dom':
+ optional: true
- '@radix-ui/react-context@1.1.3':
- resolution: {integrity: sha512-ieIFACdMpYfMEjF0rEf5KLvfVyIkOz6PDGyNnP+u+4xQ6jny3VCgA4OgXOwNx2aUkxn8zx9fiVcM8CfFYv9Lxw==}
+ '@radix-ui/react-arrow@1.1.15':
+ resolution: {integrity: sha512-v4zggRcjadnI+ClKDuijlQEW4tw3NoaeHc/PwpKnLoLLKNUG4InLegkstooLcRIUWCs+8L22dGURCVuFfOKfnA==}
peerDependencies:
'@types/react': '*'
+ '@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
+ '@types/react-dom':
+ optional: true
- '@radix-ui/react-context@1.2.2':
- resolution: {integrity: sha512-RHCUGwKHDr0hDGg4X7ma4JG4/+12qxw8rkh5QKdDldlCvtja6nUx1Ef/8HVrJze81lEsgLQlqjzjGNHantgnQA==}
+ '@radix-ui/react-aspect-ratio@1.1.15':
+ resolution: {integrity: sha512-fy+dyVR+90nelK8rqIznFlxzx7uPcGbhxH8Nfr2bHb4UfSe+e3hklOC0luK0hDwVwnRX7xTRySpsrQVeW+/oNQ==}
peerDependencies:
'@types/react': '*'
+ '@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
+ '@types/react-dom':
+ optional: true
- '@radix-ui/react-dialog@1.1.23':
- resolution: {integrity: sha512-Ksw4WeROkO4rC9k/onilX/Ao2Cr1ku1unMNH+XSCcP4jSXYu7HDsg9n4ojMjVb22XpYjAQ9qfrFlVbru1vXDUA==}
+ '@radix-ui/react-avatar@1.1.11':
+ resolution: {integrity: sha512-0Qk603AHGV28BOBO34p7IgD5m+V5Sg/YovfayABkoDDBM5d3NCx0Mp4gGrjzLGes1jV5eNOE1r3itqOR33VC6Q==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
@@ -1694,8 +1889,8 @@ packages:
'@types/react-dom':
optional: true
- '@radix-ui/react-dismissable-layer@1.1.19':
- resolution: {integrity: sha512-8g4pfOL9HoKKLWGiypT+dphVqjFfmcXO5GBnhsG6zI+lxAx/8feQpr+1LSN8Re3hiZ+XkLNS4O9ztK11/LzQ6w==}
+ '@radix-ui/react-avatar@1.2.6':
+ resolution: {integrity: sha512-4ULOTJ/mqy2hT9GlWa/MFHxHSvH3nJzHnZM1waNsc5Bonv7i70aNenghXmD97S6OJ81ekXONGGt4nT1r0PfEdA==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
@@ -1707,17 +1902,21 @@ packages:
'@types/react-dom':
optional: true
- '@radix-ui/react-focus-guards@1.1.6':
- resolution: {integrity: sha512-RNOJjfZMTyBM6xYmV3IVGXkPjIhcBAuv48POevAXwrGJhkWZ9p1rFoIS1JFooPuT193AZmRsCPhpoVJxx6OPoQ==}
+ '@radix-ui/react-checkbox@1.3.11':
+ resolution: {integrity: sha512-Gnptr9pDDQxD3hgq2dtPbtrp/c2qH1mBwIzw3X/ivrMb2e1t0jMTi606fVEqFPaQR1ggXIVQWKj3P2WW9v7zGQ==}
peerDependencies:
'@types/react': '*'
+ '@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
+ '@types/react-dom':
+ optional: true
- '@radix-ui/react-focus-scope@1.1.16':
- resolution: {integrity: sha512-wmRZ2WWLvmt6KHy2rNPOdPUjwq5xOHY02+m+udwJTn0aNIox/rkskAvJTyTLGhPK6KgrUjlJUJpgmx/+wFiFIQ==}
+ '@radix-ui/react-collapsible@1.1.20':
+ resolution: {integrity: sha512-mcGesGplBnzN2sbvJETzpCNfSMyPnb29q1GRLU+Ib7bJrpIG2ywmRoh2V5VbA2uNvKikKUlVbAPks7JDjz4A8Q==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
@@ -1729,17 +1928,39 @@ packages:
'@types/react-dom':
optional: true
- '@radix-ui/react-id@1.1.4':
- resolution: {integrity: sha512-TMQp2llA+RYn7JcjnrMnz7wN4pcVttPZnRZo52PLQsoLVKzNlVwUeHmfePgTgRluXFvlD3GD5g5MOVVTJCO0qA==}
+ '@radix-ui/react-collection@1.1.15':
+ resolution: {integrity: sha512-9W+B9NPF0NaaPh/1NJd3+KqsnlLqU9H7T2rvww+fp+T/evVXdNAyYcnfRQZFOjkR1ajQp3yORlqnI8soawLvNA==}
peerDependencies:
'@types/react': '*'
+ '@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
+ '@types/react-dom':
+ optional: true
- '@radix-ui/react-portal@1.1.17':
- resolution: {integrity: sha512-vKQLcWypUnwZVvfV7UkGahH2g6ySe8M8R+zYBwPrv5byZ9QAW6cQVvNKo7GgmD+p8aYb6D9JBuvy8/WhOno2wQ==}
+ '@radix-ui/react-compose-refs@1.1.2':
+ resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-compose-refs@1.1.5':
+ resolution: {integrity: sha512-+48PbAAbq3didjJxa+OaWY2ZwgAKsNiRGyeHKszblZMQ+kcpd9pAaT11cMkGEie0vsOi3QdeTE6d5Fe3Gn61kA==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-context-menu@2.3.7':
+ resolution: {integrity: sha512-CtXP35dxaB5T3zXSd+E3uHe/QpXcpYnZmxp6OaIbfthtfW4wyb77M23BG+bwIJDtsMwEP/YssdsmNyZu7jhWew==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
@@ -1751,8 +1972,26 @@ packages:
'@types/react-dom':
optional: true
- '@radix-ui/react-presence@1.1.10':
- resolution: {integrity: sha512-3wyzCQ6+ubRA+D4uv9m95JYLXxmOHp05qjrkjeA7uKHHtjpPggQzc6DAb0URl7j67oR0K2foO4ip27TiX037Bw==}
+ '@radix-ui/react-context@1.1.3':
+ resolution: {integrity: sha512-ieIFACdMpYfMEjF0rEf5KLvfVyIkOz6PDGyNnP+u+4xQ6jny3VCgA4OgXOwNx2aUkxn8zx9fiVcM8CfFYv9Lxw==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-context@1.2.2':
+ resolution: {integrity: sha512-RHCUGwKHDr0hDGg4X7ma4JG4/+12qxw8rkh5QKdDldlCvtja6nUx1Ef/8HVrJze81lEsgLQlqjzjGNHantgnQA==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-dialog@1.1.23':
+ resolution: {integrity: sha512-Ksw4WeROkO4rC9k/onilX/Ao2Cr1ku1unMNH+XSCcP4jSXYu7HDsg9n4ojMjVb22XpYjAQ9qfrFlVbru1vXDUA==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
@@ -1764,8 +2003,17 @@ packages:
'@types/react-dom':
optional: true
- '@radix-ui/react-primitive@2.1.10':
- resolution: {integrity: sha512-MucOnzh6hR5mid6VpkbglRAMYMjKLqRnGBbjXkzjK52fuQDd1qbkx78a5P40mkcnVXJdEVxm26E9OPAiUq7nBg==}
+ '@radix-ui/react-direction@1.1.4':
+ resolution: {integrity: sha512-5pzg4FGQNpExhnhT2zlrP1wZFaYCd1K0nYWoFAdcYoYK868IEigqMX3B3f8yIoRlAhAeDWciLI6ZdCKHF9P4Vg==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-dismissable-layer@1.1.19':
+ resolution: {integrity: sha512-8g4pfOL9HoKKLWGiypT+dphVqjFfmcXO5GBnhsG6zI+lxAx/8feQpr+1LSN8Re3hiZ+XkLNS4O9ztK11/LzQ6w==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
@@ -1777,8 +2025,8 @@ packages:
'@types/react-dom':
optional: true
- '@radix-ui/react-primitive@2.1.4':
- resolution: {integrity: sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==}
+ '@radix-ui/react-dropdown-menu@2.1.24':
+ resolution: {integrity: sha512-geq8l2rJkxvkXsT9RMgtUE3P8pITFpTsvYpbySi1IH4fZEABD/Gp85myayFgxk0ktljGMJnCbeFkyTusvSvv7g==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
@@ -1790,8 +2038,8 @@ packages:
'@types/react-dom':
optional: true
- '@radix-ui/react-slot@1.2.4':
- resolution: {integrity: sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==}
+ '@radix-ui/react-focus-guards@1.1.6':
+ resolution: {integrity: sha512-RNOJjfZMTyBM6xYmV3IVGXkPjIhcBAuv48POevAXwrGJhkWZ9p1rFoIS1JFooPuT193AZmRsCPhpoVJxx6OPoQ==}
peerDependencies:
'@types/react': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
@@ -1799,35 +2047,47 @@ packages:
'@types/react':
optional: true
- '@radix-ui/react-slot@1.3.3':
- resolution: {integrity: sha512-qx7oqnYbxnK9kYI9m317qmFmEgo6ywqWvbTogdj7cL9p3/yx4M48p7Rnw5z3H890cL/ow/EeWJsuTykeZVXP5Q==}
+ '@radix-ui/react-focus-scope@1.1.16':
+ resolution: {integrity: sha512-wmRZ2WWLvmt6KHy2rNPOdPUjwq5xOHY02+m+udwJTn0aNIox/rkskAvJTyTLGhPK6KgrUjlJUJpgmx/+wFiFIQ==}
peerDependencies:
'@types/react': '*'
+ '@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
+ '@types/react-dom':
+ optional: true
- '@radix-ui/react-use-callback-ref@1.1.1':
- resolution: {integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==}
+ '@radix-ui/react-form@0.1.16':
+ resolution: {integrity: sha512-Q4TLEn2A7TAypxwmd6R9EwrlXDvkfYSDMrq9/887AXAGh+G1rH+kYJKSTv+Si9Y0JPKTwKYv6PviAJosysNimA==}
peerDependencies:
'@types/react': '*'
+ '@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
+ '@types/react-dom':
+ optional: true
- '@radix-ui/react-use-callback-ref@1.1.4':
- resolution: {integrity: sha512-R6OUY2e2fA6Yn6s+VSx5KBV6Nx8LQEhu+cz7LCej18rQ1HLyg9PSC9jP/ZNx0o6FAIK9c0F1kHylzSxKsdlkrQ==}
+ '@radix-ui/react-hover-card@1.1.23':
+ resolution: {integrity: sha512-H8qONfZd3ltrU3+jHCIgITbWo6e1iTKvP9DHdrvYbX48ooRM5FjEDTn16AMwdfuOGkWdZEhpl3PLL/Wk/AnHDQ==}
peerDependencies:
'@types/react': '*'
+ '@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
+ '@types/react-dom':
+ optional: true
- '@radix-ui/react-use-controllable-state@1.2.6':
- resolution: {integrity: sha512-uEQJGT97ZA/TgP/Hydw47lHu+/vQj6z/0jA+WeTbK1o9Rx45GImjpD0tc3W5ad3D6XTSR6e1yEO0FvGq6WQfVQ==}
+ '@radix-ui/react-id@1.1.4':
+ resolution: {integrity: sha512-TMQp2llA+RYn7JcjnrMnz7wN4pcVttPZnRZo52PLQsoLVKzNlVwUeHmfePgTgRluXFvlD3GD5g5MOVVTJCO0qA==}
peerDependencies:
'@types/react': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
@@ -1835,53 +2095,497 @@ packages:
'@types/react':
optional: true
- '@radix-ui/react-use-effect-event@0.0.5':
- resolution: {integrity: sha512-7cshFL8HGS/7HEiHH+9kL9HBwp2sa9yX18Knwek6KYWmXwM7pegMgta2AXMQKI+rq3JnfSj9x8wYqFMTdG1Jgg==}
+ '@radix-ui/react-label@2.1.15':
+ resolution: {integrity: sha512-o/rdYEwZTTo5tjknnPeyQFU45kUC4i/XyeDPP+HGyi6XqpOP6Zf5Ya5vh/Yfe9Id5JiuWnnAx2XqIeD3UYZt0g==}
peerDependencies:
'@types/react': '*'
+ '@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
+ '@types/react-dom':
+ optional: true
- '@radix-ui/react-use-is-hydrated@0.1.0':
- resolution: {integrity: sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA==}
+ '@radix-ui/react-menu@2.1.24':
+ resolution: {integrity: sha512-uW7RVuU6Lp/ZtfeY4b3kL32zccgEWvPv1+cf17ubYzHa9cL8AHokmk36cG/XEiH/smbQvumnieXX9j/e9RqJWA==}
peerDependencies:
'@types/react': '*'
+ '@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
+ '@types/react-dom':
+ optional: true
- '@radix-ui/react-use-layout-effect@1.1.1':
- resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==}
+ '@radix-ui/react-menubar@1.1.24':
+ resolution: {integrity: sha512-eeVs0vf7cuqXaM0qLQCPcufImiJNVBXdJDLu7ZGYl2732UH23Qat/foNGrr6vYV3/DdTsBqASoggUFgH14OcZA==}
peerDependencies:
'@types/react': '*'
+ '@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
+ '@types/react-dom':
+ optional: true
- '@radix-ui/react-use-layout-effect@1.1.4':
- resolution: {integrity: sha512-K20DkRkUwDnxEYMBPcg3Y6voLkEy5p5QQmszZgLngKKiC7dzBR/aEuK3w1qlx2JWDUNH6FluahYdgR3BP+QbYw==}
+ '@radix-ui/react-navigation-menu@1.2.22':
+ resolution: {integrity: sha512-ou7iLEJ+yrhQndkkA4U21XIdS/CS45F4iXIkTZcb6/Ne9EMsOuDudVmCwmDnfFZZ+y1FZqXRNSIgBy+YMvZVZg==}
peerDependencies:
'@types/react': '*'
+ '@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
+ '@types/react-dom':
+ optional: true
- '@react-aria/color@3.2.1':
- resolution: {integrity: sha512-v/pZh1yBNeayQ5Kio0xRIX0gBf6uu+KjCRW4QSydTyRpmhvk6sMzHT9i9/vIMdaDHaYHBXlXF+Tp/mvS9CG4Zw==}
+ '@radix-ui/react-one-time-password-field@0.1.16':
+ resolution: {integrity: sha512-Tj9P6ntAJEw52oq/F0AGknXR4XncxEt7XU47O3xJQOiWfLzEy3d9gtgKfvjSzGxzHkfL+VzvxGu2KTFsloJqXw==}
peerDependencies:
- react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
- react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
- '@react-aria/focus@3.22.1':
- resolution: {integrity: sha512-CPxtkyrBi/HYY5P3lE/57sQ6qfa0lN8E55TOm89H0kNGv0lKt+/0zP7lWERzBjRr5IxBVrQX4gFEowBN52LPaA==}
+ '@radix-ui/react-password-toggle-field@0.1.11':
+ resolution: {integrity: sha512-4gvFnmDXu3dgj21CqsufzIameRvlRd4SBqaWhcrlrNhRo0Y5i/49AmRJYe1fdAM3G2VNBbmin4b0D6cdQocwgw==}
peerDependencies:
- react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
- react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-popover@1.1.23':
+ resolution: {integrity: sha512-mw58MrBlyHWFisTOYignD0vf/3gdcgAR+9of1s9G/38CbFiUwH1nCDkc0AUM9IrXFgN5Ue8n45j9WCgyM1sbiQ==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-popper@1.3.7':
+ resolution: {integrity: sha512-UsJrrd7w4wuKKTdvd/DNERVlwSlUcyXzjhyDwBk+3aPOsCjOY6ZSbxuw8E6lZTjjfP8Cpd0J8VVkrYUWyGYXyg==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-portal@1.1.17':
+ resolution: {integrity: sha512-vKQLcWypUnwZVvfV7UkGahH2g6ySe8M8R+zYBwPrv5byZ9QAW6cQVvNKo7GgmD+p8aYb6D9JBuvy8/WhOno2wQ==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-presence@1.1.10':
+ resolution: {integrity: sha512-3wyzCQ6+ubRA+D4uv9m95JYLXxmOHp05qjrkjeA7uKHHtjpPggQzc6DAb0URl7j67oR0K2foO4ip27TiX037Bw==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-primitive@2.1.10':
+ resolution: {integrity: sha512-MucOnzh6hR5mid6VpkbglRAMYMjKLqRnGBbjXkzjK52fuQDd1qbkx78a5P40mkcnVXJdEVxm26E9OPAiUq7nBg==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-primitive@2.1.4':
+ resolution: {integrity: sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-progress@1.1.16':
+ resolution: {integrity: sha512-5XnomAsoZZCY+KNTxbIghpGqPruZvKFNlvcAljVAOdDRDsH4/OZQxhtwo5wdtoDM5R6MhJBb2sPnDuRFep3lzg==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-radio-group@1.4.7':
+ resolution: {integrity: sha512-cgYFEkntCxppHZgtSZ+7vh0wbZQ+IC7PPMw8DSnRG27B6kDd32/Zw0OJt7dGDigCoprMuWHjg2PvUn3PYvPFoQ==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-roving-focus@1.1.19':
+ resolution: {integrity: sha512-V9jI6hDjT7l3jsCQD9bLNvDLM3tH/gdbOTp7Tefp3hbbgCGQoK7tUvrWiRlcoBHIZ809ElXwNQwVo0B98LuTXQ==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-scroll-area@1.2.18':
+ resolution: {integrity: sha512-Zn5Cd171wxsO3Dfg8HaW6RifTb9CYTKQJHs/G4+LN1GfmJpaQMZQyQxMprVPHpaz7QY4l9BxK2JwQuzHsXC8nA==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-select@2.3.7':
+ resolution: {integrity: sha512-WFGImkmbzcfxeIwq/+4HvRN0pizBwbwQUED4I13ezQsDdfl38ZntN6TmR8XaSzPBqoCToe8rF75j6NPNDSzhbg==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-separator@1.1.15':
+ resolution: {integrity: sha512-jOLO4lssEzWpoDu7G+Ze4VjwMRUBt291pnZD0gmalREZipnTX3wadQo7Fy48GCTfe14/YRN6rw/rOJqrE85Wxw==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-slider@1.4.7':
+ resolution: {integrity: sha512-mTSLf1GC/C0moWjTbvCM6Qn/gBjvlFt1azuWF2v7MN5C3Zq2U2J2lN3ZEYkpujuOU5Ro7A28wkviSxaKnG0BYg==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-slot@1.2.4':
+ resolution: {integrity: sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-slot@1.3.3':
+ resolution: {integrity: sha512-qx7oqnYbxnK9kYI9m317qmFmEgo6ywqWvbTogdj7cL9p3/yx4M48p7Rnw5z3H890cL/ow/EeWJsuTykeZVXP5Q==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-switch@1.3.7':
+ resolution: {integrity: sha512-48tB/4dn2UVLBCYhTu9AuR63IHl73l/qLbLgxd86noTUor4/K4LFDAcYjK+isP5313qxaFpjPVogE7+Y0/V3Kw==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-tabs@1.1.21':
+ resolution: {integrity: sha512-UKxJlZid7FVtsk/WTxj4i4uSEgj2Au+KBbS7SQyTlzMhhn+86Cz3tISZdTa87bfEfcuvZezf2ZsxD4xuEKtkog==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-toast@1.2.23':
+ resolution: {integrity: sha512-ofhyAsYaocRGOs/n0XWdUOSVzEAG6BfrMVM8z0c0kLEWY38w/0WuMFPTJP/HVaZPYkMvHZoKIIhNcjbTCBILPg==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-toggle-group@1.1.19':
+ resolution: {integrity: sha512-OtnwuSVjd1Ofi+AdnvhsjQdyuhCDwYs1w9RyB5BN/OavXOVQo42SYqQjwUnbPnaiPFBpQ9aX70dWeee+v2oBLA==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-toggle@1.1.18':
+ resolution: {integrity: sha512-7lonPlKfSacd20GlOBx2ltuVKz9oqWYZz+oMQyOltw6t1y2nyftj2ZmwwUHYn49kqfDWcp8dNZm5NgV+5Z+mug==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-toolbar@1.1.19':
+ resolution: {integrity: sha512-Ph0IvtYw4VB12ZnZg+YtrGs8yJQsnizwo/zu0R4Y/nWugtJzA7Pg1eWeuDR9+LSqn+xjamss+UOSOJJJ4gx8jw==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-tooltip@1.2.16':
+ resolution: {integrity: sha512-6EamKFRRnlpdadndbZ6LMwycfwkwPte1B42hs6QA0gYhjaOKqW4PZ4pjaW9UrlDX5eVt/OjncE7BFTPL5nmZhg==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-use-callback-ref@1.1.1':
+ resolution: {integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-use-callback-ref@1.1.4':
+ resolution: {integrity: sha512-R6OUY2e2fA6Yn6s+VSx5KBV6Nx8LQEhu+cz7LCej18rQ1HLyg9PSC9jP/ZNx0o6FAIK9c0F1kHylzSxKsdlkrQ==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-use-controllable-state@1.2.6':
+ resolution: {integrity: sha512-uEQJGT97ZA/TgP/Hydw47lHu+/vQj6z/0jA+WeTbK1o9Rx45GImjpD0tc3W5ad3D6XTSR6e1yEO0FvGq6WQfVQ==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-use-effect-event@0.0.5':
+ resolution: {integrity: sha512-7cshFL8HGS/7HEiHH+9kL9HBwp2sa9yX18Knwek6KYWmXwM7pegMgta2AXMQKI+rq3JnfSj9x8wYqFMTdG1Jgg==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-use-escape-keydown@1.1.5':
+ resolution: {integrity: sha512-ge3ipobwSXTj4JyVtswQ7qZj0ZHdtbGuOno/LrgAAeSxtsJ6Vs4Gz5IkPH2bmqpjcLUFoqGhA/mueuIf63UXlA==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-use-is-hydrated@0.1.0':
+ resolution: {integrity: sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-use-is-hydrated@0.1.3':
+ resolution: {integrity: sha512-umO/aJ+82CpOnhDZUTbILCQf7kU/g0iv+oGs/Q8jw7IkhWBzaEP4sA268PhFAJTFetbwp3ICc6ktpI4TqtxcIw==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-use-layout-effect@1.1.1':
+ resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-use-layout-effect@1.1.4':
+ resolution: {integrity: sha512-K20DkRkUwDnxEYMBPcg3Y6voLkEy5p5QQmszZgLngKKiC7dzBR/aEuK3w1qlx2JWDUNH6FluahYdgR3BP+QbYw==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-use-previous@1.1.4':
+ resolution: {integrity: sha512-XoSLhbRbqxFtgJoi2fNHA3C6pDlY34x508vUpUGoFZfvePfHXHbE1lC4FYFMnJWgiCRroSTw6fOsXQoVS9RwZg==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-use-rect@1.1.4':
+ resolution: {integrity: sha512-cSOCh6JlkmfjLyNcLiu2nB4v+nm+dkZ+Q5KHWk/soo4U7ZLiEQFKHK9/YmtBHjfCEaU43IBKQOc4/uJmCaiCTQ==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-use-size@1.1.4':
+ resolution: {integrity: sha512-D3anSY15EJoxrihpsXI6SMrmmonnQtR2ni7arO+Lfdg3O95b9hNXxONk8jA5C8ANdF/h5HMAxejgs8PWJ6rlhw==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-visually-hidden@1.2.11':
+ resolution: {integrity: sha512-NFS86RYYZb4/exihaESBGOpMJFz8MGLAfu3mOBSGByVnVPC9JPASfYubxd/8KbkQK0sYAv8lVQDEQukDX/qXvQ==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/rect@1.1.3':
+ resolution: {integrity: sha512-JtyZR+mqgBibTo8xea3B6ZRmzZiM/YeVBtUkas6zMuXjAlfIFIW2FgqeM9eLyvEaYX66vr6DJMK+4U6LV0KhNw==}
+
+ '@react-aria/color@3.2.1':
+ resolution: {integrity: sha512-v/pZh1yBNeayQ5Kio0xRIX0gBf6uu+KjCRW4QSydTyRpmhvk6sMzHT9i9/vIMdaDHaYHBXlXF+Tp/mvS9CG4Zw==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
+ react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
+
+ '@react-aria/focus@3.22.1':
+ resolution: {integrity: sha512-CPxtkyrBi/HYY5P3lE/57sQ6qfa0lN8E55TOm89H0kNGv0lKt+/0zP7lWERzBjRr5IxBVrQX4gFEowBN52LPaA==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
+ react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
'@react-aria/i18n@3.13.1':
resolution: {integrity: sha512-z56ZYcbfpNmMyiGLhyEjytpmEfoTlBaksk84q4kds3HvNkf7QWKj+DJVfVDrJX+c1LyuBsszLSX7yxJRiHsYKQ==}
@@ -2649,6 +3353,9 @@ packages:
'@types/node@26.2.0':
resolution: {integrity: sha512-5IviulTZeRNp2vAJ514cc/HUlY5nZ9fCbq9DMyC52BrhFZACo3nI0R7qBxhQmo/d27NFe96ur/b7Wwxklda+kg==}
+ '@types/prismjs@1.26.6':
+ resolution: {integrity: sha512-vqlvI7qlMvcCBbVe0AKAb4f97//Hy0EBTaiW8AalRnG/xAN5zOiWWyrNqNXeq8+KAuvRewjCVY1+IPxk4RdNYw==}
+
'@types/react-dom@19.2.4':
resolution: {integrity: sha512-Bsc+QHgp+P/F02XDzNCY9jnZNCUuLki36KT7VKrTXXLdHf+vHMNZnW1rVu5DNW/rCK+fya3DATySbLM4yhtKUw==}
peerDependencies:
@@ -3146,6 +3853,9 @@ packages:
'@vitest/utils@4.1.11':
resolution: {integrity: sha512-zTCVGpyFsGWBhllOyKlTw/vnr6D9qxsfSDyfbyZmTyjHw5N/VuvzHpHoQjm2ZJzn4RJgx5w4r7V0er69CmLgPQ==}
+ '@workflow/serde@4.1.0':
+ resolution: {integrity: sha512-pav4F2BoirECWR7Nf1TKt+2eETcBj7jj4cBefQ8VXQCA6NPkaKeLfj/zMgi+3zYV5ZIBT4GuUiphsj0/b9hPQQ==}
+
'@workflow/serde@4.1.0-beta.2':
resolution: {integrity: sha512-8kkeoQKLDaKXefjV5dbhBj2aErfKp1Mc4pb6tj8144cF+Em5SPbyMbyLCHp+BVrFfFVCBluCtMx+jjvaFVZGww==}
@@ -3202,6 +3912,12 @@ packages:
resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==}
engines: {node: '>= 14'}
+ ai@7.0.77:
+ resolution: {integrity: sha512-muLtBSTAUCreR77L16w4AFBiX2gK/RNt84EKp8m03SN9+MfNlC5EGqYYttRjYKV3xe0a33yj1Zawj1EnjejIWw==}
+ engines: {node: '>=22'}
+ peerDependencies:
+ zod: ^3.25.76 || ^4.1.8
+
ajv-formats@2.1.1:
resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==}
peerDependencies:
@@ -3335,6 +4051,20 @@ packages:
resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
engines: {node: '>=12'}
+ assistant-cloud@0.1.41:
+ resolution: {integrity: sha512-lrH9USOoNaAWAAbujeHa/PEiWoqNQHjIPIAeeA3y3GUXOdlmTjIyR/7GbEZBKbHhm1Lyt7aAYKvdxHFkhuEbJw==}
+
+ assistant-stream@0.3.39:
+ resolution: {integrity: sha512-Ad28saCwQxqaB69w4WNaS3uW5OW5BgTYnzTfbgFnz1CPXZjcWFsMrHhcJUpR4+lfYPTNal0oHEb47VUZH4Zz1g==}
+ peerDependencies:
+ ioredis: ^5.10.1 || ^6.0.0
+ redis: ^5.12.1
+ peerDependenciesMeta:
+ ioredis:
+ optional: true
+ redis:
+ optional: true
+
ast-types-flow@0.0.8:
resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==}
@@ -3614,6 +4344,9 @@ packages:
resolution: {integrity: sha512-yKNcXi/Mvi5kb1uK0sahubYiyfUO2EUgOp4NcY9+8NX5Xmc+4yeNogZuLFkpLBBj7/QI9MjRUIuXrV9XOw5kVg==}
engines: {node: '>= 0.3.0'}
+ classnames@2.5.1:
+ resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==}
+
cli-boxes@2.2.1:
resolution: {integrity: sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==}
engines: {node: '>=6'}
@@ -4589,6 +5322,9 @@ packages:
fastq@1.20.1:
resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==}
+ fault@1.0.4:
+ resolution: {integrity: sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==}
+
fault@2.0.1:
resolution: {integrity: sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==}
@@ -5003,6 +5739,9 @@ packages:
highlight.js@10.7.3:
resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==}
+ highlightjs-vue@1.0.0:
+ resolution: {integrity: sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA==}
+
hono@4.13.3:
resolution: {integrity: sha512-r8AO2mYHoLxSHkgafNeC/BXyb2vWRxD3jem4Ts+ptav8oTG5FIRifAjuJEmZI4bSvvc2ns0GxmIYiZnHqN3mMw==}
engines: {node: '>=16.9.0'}
@@ -5014,6 +5753,9 @@ packages:
html-escaper@2.0.2:
resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==}
+ html-url-attributes@3.0.1:
+ resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==}
+
html-void-elements@3.0.0:
resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==}
@@ -5474,6 +6216,9 @@ packages:
json-schema-typed@8.0.2:
resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==}
+ json-schema@0.4.0:
+ resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==}
+
json-stable-stringify-without-jsonify@1.0.1:
resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
@@ -5758,6 +6503,9 @@ packages:
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
hasBin: true
+ lowlight@1.20.0:
+ resolution: {integrity: sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==}
+
lru-cache@10.4.3:
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
@@ -6156,6 +6904,11 @@ packages:
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
+ nanoid@6.0.1:
+ resolution: {integrity: sha512-3wVS3i51pE2pi1k5FFL/95BGfVS0kSsvDVuGXHOtxox/TywUmtgq+3qiTOTbs9J7KfHaXPiN171k/A6dBnaXFw==}
+ engines: {node: ^22 || ^24 || >=26}
+ hasBin: true
+
napi-postinstall@0.3.4:
resolution: {integrity: sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==}
engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
@@ -6658,6 +7411,10 @@ packages:
resolution: {integrity: sha512-973driJZvxiGOQ5ONsFhOF/DtzPMOMtgC11kCpUrPGMTgqp2q/1gwzCquocrN33is0VZ5GFHXZYMM9l6h67v2Q==}
engines: {node: '>=10'}
+ prismjs@1.30.0:
+ resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==}
+ engines: {node: '>=6'}
+
proc-log@7.0.0:
resolution: {integrity: sha512-FYgfaA69XZ93zaXLoMNQ+ViDXGGBgR8aLh03txzcFhV+9xOXx7+8DLCULrKKpR9+GsH9ZfHm82aSUPpozX0Ztg==}
engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0}
@@ -6725,6 +7482,19 @@ packages:
queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
+ radix-ui@1.6.7:
+ resolution: {integrity: sha512-QBdhh1arIEUvPC0dQ5+nwWAxt7+N+oP/9jPwjJkGFoSk/sqxg32gJtSXGtFh8frAIcS6oC9cx2Q+7KYCQLOAeA==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
railroad-diagrams@1.0.0:
resolution: {integrity: sha512-cz93DjNeLY0idrCNOH6PviZGRN9GJhsdm9hpn1YCS879fj4W+x5IFJhhkRZcwVgMmFF7R82UA/7Oh+R8lLZg6A==}
@@ -6806,6 +7576,12 @@ packages:
react-is@17.0.2:
resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==}
+ react-markdown@10.1.0:
+ resolution: {integrity: sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==}
+ peerDependencies:
+ '@types/react': '>=18'
+ react: '>=18'
+
react-medium-image-zoom@5.4.8:
resolution: {integrity: sha512-72CIldEUaPejjcaDOYIeDsGlWzNKpmxyKgiPi1LBCkWCIGHDLlA2KTeo2uNtmN/m72Y3k/2uWDfon9SDiPbHaA==}
peerDependencies:
@@ -6857,6 +7633,18 @@ packages:
'@types/react':
optional: true
+ react-syntax-highlighter@16.1.1:
+ resolution: {integrity: sha512-PjVawBGy80C6YbC5DDZJeUjBmC7skaoEUdvfFQediQHgCL7aKyVHe57SaJGfQsloGDac+gCpTfRdtxzWWKmCXA==}
+ engines: {node: '>= 16.20.2'}
+ peerDependencies:
+ react: '>= 0.14.0'
+
+ react-textarea-autosize@8.5.9:
+ resolution: {integrity: sha512-U1DGlIQN5AwgjTyOEnI1oCcMuEr1pv1qOtklB2l4nyMGbHzWrI0eFsYK0zos2YWqAolJyG0IWJaqWmWj5ETh0A==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
react-transition-group@4.4.5:
resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==}
peerDependencies:
@@ -6914,6 +7702,9 @@ packages:
resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==}
engines: {node: '>= 0.4'}
+ refractor@5.0.0:
+ resolution: {integrity: sha512-QXOrHQF5jOpjjLfiNk5GFnWhRXvxjUVnlFxkeDmewR5sXkr3iM46Zo+CnRR8B+MDVqkULW4EcLVcRBNOPXHosw==}
+
regex-recursion@6.0.2:
resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==}
@@ -7086,6 +7877,9 @@ packages:
safe-buffer@5.2.1:
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
+ safe-content-frame@0.0.27:
+ resolution: {integrity: sha512-IFUSMjElIp8q46wUU5HViHNEqoDWRlCzqqThQmhOLimuXPe/QC6Jr/AQ+BKxSYziQdNXUpapBq4Ir/t6dS0Ldg==}
+
safe-push-apply@1.0.0:
resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==}
engines: {node: '>= 0.4'}
@@ -7115,6 +7909,9 @@ packages:
scroll-into-view-if-needed@3.1.0:
resolution: {integrity: sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==}
+ secure-json-parse@4.1.0:
+ resolution: {integrity: sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==}
+
semver-diff@3.1.1:
resolution: {integrity: sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==}
engines: {node: '>=8'}
@@ -7446,6 +8243,11 @@ packages:
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
engines: {node: '>= 0.4'}
+ swr@2.5.1:
+ resolution: {integrity: sha512-BRw55e8r0B7SpDN20CAzoQAHl7y1yP7/Zt7oqUjMv0vSt2u2Xnkm88Ws+VypbV9BXHQVuSuyVq7zMjO16wSExw==}
+ peerDependencies:
+ react: ^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
symbol-tree@3.2.4:
resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
@@ -7803,6 +8605,33 @@ packages:
'@types/react':
optional: true
+ use-composed-ref@1.4.0:
+ resolution: {integrity: sha512-djviaxuOOh7wkj0paeO1Q/4wMZ8Zrnag5H6yBvzN7AKKe8beOaED9SF5/ByLqsku8NP4zQqsvM2u3ew/tJK8/w==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ use-isomorphic-layout-effect@1.2.1:
+ resolution: {integrity: sha512-tpZZ+EX0gaghDAiFR37hj5MgY6ZN55kLiPkJsKxBMZ6GZdOSPJXiOzPM984oPYZ5AnehYx5WQp1+ME8I/P/pRA==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ use-latest@1.3.0:
+ resolution: {integrity: sha512-mhg3xdm9NaM8q+gLT8KryJPnRFOz1/5XPBhmDEVZK1webPzDjrPk7f/mbpeLqTgB9msytYWANxgALOCJKnLvcQ==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
use-sidecar@1.1.3:
resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==}
engines: {node: '>=10'}
@@ -8196,6 +9025,24 @@ packages:
use-sync-external-store:
optional: true
+ zustand@5.0.15:
+ resolution: {integrity: sha512-MpSEjRiBkA9crSYeOUH32rJC7SVqAbm0Fqcqge/bUi2PPoLcBWKOsG+C8mevmpr8TwXHBVkChbbJiyvkE+i/3A==}
+ engines: {node: '>=12.20.0'}
+ peerDependencies:
+ '@types/react': '>=18.0.0'
+ immer: '>=9.0.6'
+ react: '>=18.0.0'
+ use-sync-external-store: '>=1.2.0'
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ immer:
+ optional: true
+ react:
+ optional: true
+ use-sync-external-store:
+ optional: true
+
zwitch@2.0.4:
resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
@@ -8247,6 +9094,64 @@ snapshots:
react-transition-group: 4.4.5(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
use-sync-external-store: 1.6.0(react@19.2.8)
+ '@ai-sdk/anthropic@4.0.41(zod@4.4.3)':
+ dependencies:
+ '@ai-sdk/provider': 4.0.7
+ '@ai-sdk/provider-utils': 5.0.29(zod@4.4.3)
+ zod: 4.4.3
+
+ '@ai-sdk/gateway@4.0.62(zod@4.4.3)':
+ dependencies:
+ '@ai-sdk/provider': 4.0.7
+ '@ai-sdk/provider-utils': 5.0.29(zod@4.4.3)
+ '@vercel/oidc': 3.2.0
+ zod: 4.4.3
+
+ '@ai-sdk/google@4.0.50(zod@4.4.3)':
+ dependencies:
+ '@ai-sdk/provider': 4.0.7
+ '@ai-sdk/provider-utils': 5.0.29(zod@4.4.3)
+ zod: 4.4.3
+
+ '@ai-sdk/mcp@2.0.36(zod@4.4.3)':
+ dependencies:
+ '@ai-sdk/provider': 4.0.7
+ '@ai-sdk/provider-utils': 5.0.29(zod@4.4.3)
+ cross-spawn: 7.0.6
+ pkce-challenge: 5.0.1
+ zod: 4.4.3
+
+ '@ai-sdk/openai@4.0.46(zod@4.4.3)':
+ dependencies:
+ '@ai-sdk/provider': 4.0.7
+ '@ai-sdk/provider-utils': 5.0.29(zod@4.4.3)
+ zod: 4.4.3
+
+ '@ai-sdk/provider-utils@5.0.29(zod@4.4.3)':
+ dependencies:
+ '@ai-sdk/provider': 4.0.7
+ '@standard-schema/spec': 1.1.0
+ '@workflow/serde': 4.1.0
+ eventsource-parser: 3.1.1
+ undici: 6.28.0
+ zod: 4.4.3
+
+ '@ai-sdk/provider@4.0.7':
+ dependencies:
+ json-schema: 0.4.0
+
+ '@ai-sdk/react@4.0.80(react@19.2.8)(zod@4.4.3)':
+ dependencies:
+ '@ai-sdk/mcp': 2.0.36(zod@4.4.3)
+ '@ai-sdk/provider': 4.0.7
+ '@ai-sdk/provider-utils': 5.0.29(zod@4.4.3)
+ ai: 7.0.77(zod@4.4.3)
+ react: 19.2.8
+ swr: 2.5.1(react@19.2.8)
+ throttleit: 2.1.0
+ transitivePeerDependencies:
+ - zod
+
'@alloc/quick-lru@5.2.0': {}
'@antfu/install-pkg@1.1.0':
@@ -8263,20 +9168,133 @@ snapshots:
'@apphosting/common@0.0.8': {}
- '@asamuzakjp/css-color@6.0.7':
+ '@asamuzakjp/css-color@6.0.7':
+ dependencies:
+ '@csstools/css-calc': 3.3.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)
+ '@csstools/css-color-parser': 4.2.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)
+ '@csstools/css-tokenizer': 4.0.0
+ lru-cache: 11.5.2
+
+ '@asamuzakjp/dom-selector@8.3.2':
+ dependencies:
+ bidi-js: 1.0.3
+ css-tree: 3.2.1
+ is-potential-custom-element-name: 1.0.1
+ lru-cache: 11.5.2
+
+ '@assistant-ui/ai-sdk@0.0.2(@types/react@19.2.18)(react@19.2.8)(zod@4.4.3)(zustand@5.0.15(@types/react@19.2.18)(react@19.2.8)(use-sync-external-store@1.6.0(react@19.2.8)))':
+ dependencies:
+ '@ai-sdk/mcp': 2.0.36(zod@4.4.3)
+ '@ai-sdk/react': 4.0.80(react@19.2.8)(zod@4.4.3)
+ '@assistant-ui/core': 0.3.15(@assistant-ui/store@0.3.10(@assistant-ui/tap@0.9.14(@types/react@19.2.18)(react@19.2.8))(@types/react@19.2.18)(react@19.2.8))(@assistant-ui/tap@0.9.14(@types/react@19.2.18)(react@19.2.8))(@types/react@19.2.18)(assistant-cloud@0.1.41)(react@19.2.8)(zustand@5.0.15(@types/react@19.2.18)(react@19.2.8)(use-sync-external-store@1.6.0(react@19.2.8)))
+ '@assistant-ui/store': 0.3.10(@assistant-ui/tap@0.9.14(@types/react@19.2.18)(react@19.2.8))(@types/react@19.2.18)(react@19.2.8)
+ '@assistant-ui/tap': 0.9.14(@types/react@19.2.18)(react@19.2.8)
+ ai: 7.0.77(zod@4.4.3)
+ assistant-cloud: 0.1.41
+ assistant-stream: 0.3.39
+ optionalDependencies:
+ '@types/react': 19.2.18
+ react: 19.2.8
+ transitivePeerDependencies:
+ - ioredis
+ - redis
+ - zod
+ - zustand
+
+ '@assistant-ui/core@0.3.15(@assistant-ui/store@0.3.10(@assistant-ui/tap@0.9.14(@types/react@19.2.18)(react@19.2.8))(@types/react@19.2.18)(react@19.2.8))(@assistant-ui/tap@0.9.14(@types/react@19.2.18)(react@19.2.8))(@types/react@19.2.18)(assistant-cloud@0.1.41)(react@19.2.8)(zustand@5.0.15(@types/react@19.2.18)(react@19.2.8)(use-sync-external-store@1.6.0(react@19.2.8)))':
+ dependencies:
+ '@assistant-ui/store': 0.3.10(@assistant-ui/tap@0.9.14(@types/react@19.2.18)(react@19.2.8))(@types/react@19.2.18)(react@19.2.8)
+ '@assistant-ui/tap': 0.9.14(@types/react@19.2.18)(react@19.2.8)
+ assistant-stream: 0.3.39
+ nanoid: 6.0.1
+ optionalDependencies:
+ '@types/react': 19.2.18
+ assistant-cloud: 0.1.41
+ react: 19.2.8
+ zustand: 5.0.15(@types/react@19.2.18)(react@19.2.8)(use-sync-external-store@1.6.0(react@19.2.8))
+ transitivePeerDependencies:
+ - ioredis
+ - redis
+
+ '@assistant-ui/react-ai-sdk@1.4.7(@types/react@19.2.18)(react@19.2.8)(zod@4.4.3)(zustand@5.0.15(@types/react@19.2.18)(react@19.2.8)(use-sync-external-store@1.6.0(react@19.2.8)))':
+ dependencies:
+ '@assistant-ui/ai-sdk': 0.0.2(@types/react@19.2.18)(react@19.2.8)(zod@4.4.3)(zustand@5.0.15(@types/react@19.2.18)(react@19.2.8)(use-sync-external-store@1.6.0(react@19.2.8)))
+ react: 19.2.8
+ optionalDependencies:
+ '@types/react': 19.2.18
+ transitivePeerDependencies:
+ - ioredis
+ - redis
+ - zod
+ - zustand
+
+ '@assistant-ui/react-markdown@0.14.12(@assistant-ui/react@0.15.16(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(use-sync-external-store@1.6.0(react@19.2.8)))(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(supports-color@7.2.0)':
+ dependencies:
+ '@assistant-ui/react': 0.15.16(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(use-sync-external-store@1.6.0(react@19.2.8))
+ '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.18)(react@19.2.8)
+ classnames: 2.5.1
+ react: 19.2.8
+ react-markdown: 10.1.0(@types/react@19.2.18)(react@19.2.8)(supports-color@7.2.0)
+ optionalDependencies:
+ '@types/react': 19.2.18
+ transitivePeerDependencies:
+ - '@types/react-dom'
+ - react-dom
+ - supports-color
+
+ '@assistant-ui/react-syntax-highlighter@0.14.5(@assistant-ui/react-markdown@0.14.12(@assistant-ui/react@0.15.16(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(use-sync-external-store@1.6.0(react@19.2.8)))(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(supports-color@7.2.0))(@assistant-ui/react@0.15.16(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(use-sync-external-store@1.6.0(react@19.2.8)))(@types/react@19.2.18)(react-syntax-highlighter@16.1.1(react@19.2.8))(react@19.2.8)':
+ dependencies:
+ '@assistant-ui/react': 0.15.16(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(use-sync-external-store@1.6.0(react@19.2.8))
+ '@assistant-ui/react-markdown': 0.14.12(@assistant-ui/react@0.15.16(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(use-sync-external-store@1.6.0(react@19.2.8)))(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(supports-color@7.2.0)
+ react: 19.2.8
+ react-syntax-highlighter: 16.1.1(react@19.2.8)
+ optionalDependencies:
+ '@types/react': 19.2.18
+
+ '@assistant-ui/react@0.15.16(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(use-sync-external-store@1.6.0(react@19.2.8))':
+ dependencies:
+ '@assistant-ui/core': 0.3.15(@assistant-ui/store@0.3.10(@assistant-ui/tap@0.9.14(@types/react@19.2.18)(react@19.2.8))(@types/react@19.2.18)(react@19.2.8))(@assistant-ui/tap@0.9.14(@types/react@19.2.18)(react@19.2.8))(@types/react@19.2.18)(assistant-cloud@0.1.41)(react@19.2.8)(zustand@5.0.15(@types/react@19.2.18)(react@19.2.8)(use-sync-external-store@1.6.0(react@19.2.8)))
+ '@assistant-ui/store': 0.3.10(@assistant-ui/tap@0.9.14(@types/react@19.2.18)(react@19.2.8))(@types/react@19.2.18)(react@19.2.8)
+ '@assistant-ui/tap': 0.9.14(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/primitive': 1.1.7
+ '@radix-ui/react-collection': 1.1.15(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-use-escape-keydown': 1.1.5(@types/react@19.2.18)(react@19.2.8)
+ assistant-cloud: 0.1.41
+ assistant-stream: 0.3.39
+ radix-ui: 1.6.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ react: 19.2.8
+ react-dom: 19.2.8(react@19.2.8)
+ react-textarea-autosize: 8.5.9(@types/react@19.2.18)(react@19.2.8)
+ safe-content-frame: 0.0.27
+ zod: 4.4.3
+ zustand: 5.0.15(@types/react@19.2.18)(react@19.2.8)(use-sync-external-store@1.6.0(react@19.2.8))
+ optionalDependencies:
+ '@types/react': 19.2.18
+ '@types/react-dom': 19.2.4(@types/react@19.2.18)
+ transitivePeerDependencies:
+ - immer
+ - ioredis
+ - redis
+ - use-sync-external-store
+
+ '@assistant-ui/store@0.3.10(@assistant-ui/tap@0.9.14(@types/react@19.2.18)(react@19.2.8))(@types/react@19.2.18)(react@19.2.8)':
dependencies:
- '@csstools/css-calc': 3.3.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)
- '@csstools/css-color-parser': 4.2.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)
- '@csstools/css-tokenizer': 4.0.0
- lru-cache: 11.5.2
+ '@assistant-ui/tap': 0.9.14(@types/react@19.2.18)(react@19.2.8)
+ optionalDependencies:
+ '@types/react': 19.2.18
+ react: 19.2.8
- '@asamuzakjp/dom-selector@8.3.2':
- dependencies:
- bidi-js: 1.0.3
- css-tree: 3.2.1
- is-potential-custom-element-name: 1.0.1
- lru-cache: 11.5.2
+ '@assistant-ui/tap@0.9.14(@types/react@19.2.18)(react@19.2.8)':
+ optionalDependencies:
+ '@types/react': 19.2.18
+ react: 19.2.8
'@axe-core/playwright@4.13.0(playwright-core@1.62.1)':
dependencies:
@@ -9407,118 +10425,618 @@ snapshots:
'@oxc-transform/binding-openharmony-arm64@0.111.0':
optional: true
- '@oxc-transform/binding-wasm32-wasi@0.111.0(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3)':
+ '@oxc-transform/binding-wasm32-wasi@0.111.0(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3)':
+ dependencies:
+ '@napi-rs/wasm-runtime': 1.2.3(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3)
+ transitivePeerDependencies:
+ - '@emnapi/core'
+ - '@emnapi/runtime'
+ optional: true
+
+ '@oxc-transform/binding-win32-arm64-msvc@0.111.0':
+ optional: true
+
+ '@oxc-transform/binding-win32-ia32-msvc@0.111.0':
+ optional: true
+
+ '@oxc-transform/binding-win32-x64-msvc@0.111.0':
+ optional: true
+
+ '@pkgjs/parseargs@0.11.0':
+ optional: true
+
+ '@playwright/test@1.62.1':
+ dependencies:
+ playwright: 1.62.1
+
+ '@pnpm/config.env-replace@1.1.0': {}
+
+ '@pnpm/network.ca-file@1.0.2':
+ dependencies:
+ graceful-fs: 4.2.10
+
+ '@pnpm/npm-conf@3.0.3':
+ dependencies:
+ '@pnpm/config.env-replace': 1.1.0
+ '@pnpm/network.ca-file': 1.0.2
+ config-chain: 1.1.13
+
+ '@polka/url@1.0.0-next.29': {}
+
+ '@protobufjs/aspromise@1.1.2': {}
+
+ '@protobufjs/base64@1.1.2': {}
+
+ '@protobufjs/codegen@2.0.5': {}
+
+ '@protobufjs/eventemitter@1.1.1': {}
+
+ '@protobufjs/fetch@1.1.1':
+ dependencies:
+ '@protobufjs/aspromise': 1.1.2
+
+ '@protobufjs/float@1.0.2': {}
+
+ '@protobufjs/path@1.1.2': {}
+
+ '@protobufjs/pool@1.1.0': {}
+
+ '@protobufjs/utf8@1.1.2': {}
+
+ '@radix-ui/number@1.1.3': {}
+
+ '@radix-ui/primitive@1.1.7': {}
+
+ '@radix-ui/react-accessible-icon@1.1.15(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
+ dependencies:
+ '@radix-ui/react-visually-hidden': 1.2.11(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ react: 19.2.8
+ react-dom: 19.2.8(react@19.2.8)
+ optionalDependencies:
+ '@types/react': 19.2.18
+ '@types/react-dom': 19.2.4(@types/react@19.2.18)
+
+ '@radix-ui/react-accordion@1.2.20(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.7
+ '@radix-ui/react-collapsible': 1.1.20(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-collection': 1.1.15(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-direction': 1.1.4(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-id': 1.1.4(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.8)
+ react: 19.2.8
+ react-dom: 19.2.8(react@19.2.8)
+ optionalDependencies:
+ '@types/react': 19.2.18
+ '@types/react-dom': 19.2.4(@types/react@19.2.18)
+
+ '@radix-ui/react-alert-dialog@1.1.23(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.7
+ '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-dialog': 1.1.23(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ react: 19.2.8
+ react-dom: 19.2.8(react@19.2.8)
+ optionalDependencies:
+ '@types/react': 19.2.18
+ '@types/react-dom': 19.2.4(@types/react@19.2.18)
+
+ '@radix-ui/react-arrow@1.1.15(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
+ dependencies:
+ '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ react: 19.2.8
+ react-dom: 19.2.8(react@19.2.8)
+ optionalDependencies:
+ '@types/react': 19.2.18
+ '@types/react-dom': 19.2.4(@types/react@19.2.18)
+
+ '@radix-ui/react-aspect-ratio@1.1.15(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
+ dependencies:
+ '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ react: 19.2.8
+ react-dom: 19.2.8(react@19.2.8)
+ optionalDependencies:
+ '@types/react': 19.2.18
+ '@types/react-dom': 19.2.4(@types/react@19.2.18)
+
+ '@radix-ui/react-avatar@1.1.11(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
+ dependencies:
+ '@radix-ui/react-context': 1.1.3(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-primitive': 2.1.4(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.18)(react@19.2.8)
+ react: 19.2.8
+ react-dom: 19.2.8(react@19.2.8)
+ optionalDependencies:
+ '@types/react': 19.2.18
+ '@types/react-dom': 19.2.4(@types/react@19.2.18)
+
+ '@radix-ui/react-avatar@1.2.6(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.7
+ '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-use-is-hydrated': 0.1.3(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.8)
+ react: 19.2.8
+ react-dom: 19.2.8(react@19.2.8)
+ optionalDependencies:
+ '@types/react': 19.2.18
+ '@types/react-dom': 19.2.4(@types/react@19.2.18)
+
+ '@radix-ui/react-checkbox@1.3.11(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.7
+ '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-use-size': 1.1.4(@types/react@19.2.18)(react@19.2.8)
+ react: 19.2.8
+ react-dom: 19.2.8(react@19.2.8)
+ optionalDependencies:
+ '@types/react': 19.2.18
+ '@types/react-dom': 19.2.4(@types/react@19.2.18)
+
+ '@radix-ui/react-collapsible@1.1.20(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.7
+ '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-id': 1.1.4(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.8)
+ react: 19.2.8
+ react-dom: 19.2.8(react@19.2.8)
+ optionalDependencies:
+ '@types/react': 19.2.18
+ '@types/react-dom': 19.2.4(@types/react@19.2.18)
+
+ '@radix-ui/react-collection@1.1.15(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
+ dependencies:
+ '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-slot': 1.3.3(@types/react@19.2.18)(react@19.2.8)
+ react: 19.2.8
+ react-dom: 19.2.8(react@19.2.8)
+ optionalDependencies:
+ '@types/react': 19.2.18
+ '@types/react-dom': 19.2.4(@types/react@19.2.18)
+
+ '@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.18)(react@19.2.8)':
+ dependencies:
+ react: 19.2.8
+ optionalDependencies:
+ '@types/react': 19.2.18
+
+ '@radix-ui/react-compose-refs@1.1.5(@types/react@19.2.18)(react@19.2.8)':
+ dependencies:
+ react: 19.2.8
+ optionalDependencies:
+ '@types/react': 19.2.18
+
+ '@radix-ui/react-context-menu@2.3.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.7
+ '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-menu': 2.1.24(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.8)
+ react: 19.2.8
+ react-dom: 19.2.8(react@19.2.8)
+ optionalDependencies:
+ '@types/react': 19.2.18
+ '@types/react-dom': 19.2.4(@types/react@19.2.18)
+
+ '@radix-ui/react-context@1.1.3(@types/react@19.2.18)(react@19.2.8)':
+ dependencies:
+ react: 19.2.8
+ optionalDependencies:
+ '@types/react': 19.2.18
+
+ '@radix-ui/react-context@1.2.2(@types/react@19.2.18)(react@19.2.8)':
+ dependencies:
+ react: 19.2.8
+ optionalDependencies:
+ '@types/react': 19.2.18
+
+ '@radix-ui/react-dialog@1.1.23(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.7
+ '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-focus-guards': 1.1.6(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-focus-scope': 1.1.16(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-id': 1.1.4(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-portal': 1.1.17(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-slot': 1.3.3(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.8)
+ aria-hidden: 1.2.6
+ react: 19.2.8
+ react-dom: 19.2.8(react@19.2.8)
+ react-remove-scroll: 2.7.2(@types/react@19.2.18)(react@19.2.8)
+ optionalDependencies:
+ '@types/react': 19.2.18
+ '@types/react-dom': 19.2.4(@types/react@19.2.18)
+
+ '@radix-ui/react-direction@1.1.4(@types/react@19.2.18)(react@19.2.8)':
dependencies:
- '@napi-rs/wasm-runtime': 1.2.3(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3)
- transitivePeerDependencies:
- - '@emnapi/core'
- - '@emnapi/runtime'
- optional: true
-
- '@oxc-transform/binding-win32-arm64-msvc@0.111.0':
- optional: true
+ react: 19.2.8
+ optionalDependencies:
+ '@types/react': 19.2.18
- '@oxc-transform/binding-win32-ia32-msvc@0.111.0':
- optional: true
+ '@radix-ui/react-dismissable-layer@1.1.19(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.7
+ '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-use-effect-event': 0.0.5(@types/react@19.2.18)(react@19.2.8)
+ react: 19.2.8
+ react-dom: 19.2.8(react@19.2.8)
+ optionalDependencies:
+ '@types/react': 19.2.18
+ '@types/react-dom': 19.2.4(@types/react@19.2.18)
- '@oxc-transform/binding-win32-x64-msvc@0.111.0':
- optional: true
+ '@radix-ui/react-dropdown-menu@2.1.24(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.7
+ '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-id': 1.1.4(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-menu': 2.1.24(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.8)
+ react: 19.2.8
+ react-dom: 19.2.8(react@19.2.8)
+ optionalDependencies:
+ '@types/react': 19.2.18
+ '@types/react-dom': 19.2.4(@types/react@19.2.18)
- '@pkgjs/parseargs@0.11.0':
- optional: true
+ '@radix-ui/react-focus-guards@1.1.6(@types/react@19.2.18)(react@19.2.8)':
+ dependencies:
+ react: 19.2.8
+ optionalDependencies:
+ '@types/react': 19.2.18
- '@playwright/test@1.62.1':
+ '@radix-ui/react-focus-scope@1.1.16(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
dependencies:
- playwright: 1.62.1
+ '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.18)(react@19.2.8)
+ react: 19.2.8
+ react-dom: 19.2.8(react@19.2.8)
+ optionalDependencies:
+ '@types/react': 19.2.18
+ '@types/react-dom': 19.2.4(@types/react@19.2.18)
- '@pnpm/config.env-replace@1.1.0': {}
+ '@radix-ui/react-form@0.1.16(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.7
+ '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-id': 1.1.4(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-label': 2.1.15(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ react: 19.2.8
+ react-dom: 19.2.8(react@19.2.8)
+ optionalDependencies:
+ '@types/react': 19.2.18
+ '@types/react-dom': 19.2.4(@types/react@19.2.18)
- '@pnpm/network.ca-file@1.0.2':
+ '@radix-ui/react-hover-card@1.1.23(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
dependencies:
- graceful-fs: 4.2.10
+ '@radix-ui/primitive': 1.1.7
+ '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-popper': 1.3.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-portal': 1.1.17(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.8)
+ react: 19.2.8
+ react-dom: 19.2.8(react@19.2.8)
+ optionalDependencies:
+ '@types/react': 19.2.18
+ '@types/react-dom': 19.2.4(@types/react@19.2.18)
- '@pnpm/npm-conf@3.0.3':
+ '@radix-ui/react-id@1.1.4(@types/react@19.2.18)(react@19.2.8)':
dependencies:
- '@pnpm/config.env-replace': 1.1.0
- '@pnpm/network.ca-file': 1.0.2
- config-chain: 1.1.13
+ '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.8)
+ react: 19.2.8
+ optionalDependencies:
+ '@types/react': 19.2.18
- '@polka/url@1.0.0-next.29': {}
+ '@radix-ui/react-label@2.1.15(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
+ dependencies:
+ '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ react: 19.2.8
+ react-dom: 19.2.8(react@19.2.8)
+ optionalDependencies:
+ '@types/react': 19.2.18
+ '@types/react-dom': 19.2.4(@types/react@19.2.18)
- '@protobufjs/aspromise@1.1.2': {}
+ '@radix-ui/react-menu@2.1.24(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.7
+ '@radix-ui/react-collection': 1.1.15(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-direction': 1.1.4(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-focus-guards': 1.1.6(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-focus-scope': 1.1.16(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-id': 1.1.4(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-popper': 1.3.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-portal': 1.1.17(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-roving-focus': 1.1.19(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-slot': 1.3.3(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.18)(react@19.2.8)
+ aria-hidden: 1.2.6
+ react: 19.2.8
+ react-dom: 19.2.8(react@19.2.8)
+ react-remove-scroll: 2.7.2(@types/react@19.2.18)(react@19.2.8)
+ optionalDependencies:
+ '@types/react': 19.2.18
+ '@types/react-dom': 19.2.4(@types/react@19.2.18)
- '@protobufjs/base64@1.1.2': {}
+ '@radix-ui/react-menubar@1.1.24(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.7
+ '@radix-ui/react-collection': 1.1.15(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-direction': 1.1.4(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-id': 1.1.4(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-menu': 2.1.24(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-roving-focus': 1.1.19(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.8)
+ react: 19.2.8
+ react-dom: 19.2.8(react@19.2.8)
+ optionalDependencies:
+ '@types/react': 19.2.18
+ '@types/react-dom': 19.2.4(@types/react@19.2.18)
- '@protobufjs/codegen@2.0.5': {}
+ '@radix-ui/react-navigation-menu@1.2.22(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.7
+ '@radix-ui/react-collection': 1.1.15(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-direction': 1.1.4(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-id': 1.1.4(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-use-previous': 1.1.4(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-visually-hidden': 1.2.11(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ react: 19.2.8
+ react-dom: 19.2.8(react@19.2.8)
+ optionalDependencies:
+ '@types/react': 19.2.18
+ '@types/react-dom': 19.2.4(@types/react@19.2.18)
- '@protobufjs/eventemitter@1.1.1': {}
+ '@radix-ui/react-one-time-password-field@0.1.16(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
+ dependencies:
+ '@radix-ui/number': 1.1.3
+ '@radix-ui/primitive': 1.1.7
+ '@radix-ui/react-collection': 1.1.15(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-direction': 1.1.4(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-roving-focus': 1.1.19(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-use-effect-event': 0.0.5(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-use-is-hydrated': 0.1.3(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.8)
+ react: 19.2.8
+ react-dom: 19.2.8(react@19.2.8)
+ optionalDependencies:
+ '@types/react': 19.2.18
+ '@types/react-dom': 19.2.4(@types/react@19.2.18)
- '@protobufjs/fetch@1.1.1':
+ '@radix-ui/react-password-toggle-field@0.1.11(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
dependencies:
- '@protobufjs/aspromise': 1.1.2
+ '@radix-ui/primitive': 1.1.7
+ '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-id': 1.1.4(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-use-effect-event': 0.0.5(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-use-is-hydrated': 0.1.3(@types/react@19.2.18)(react@19.2.8)
+ react: 19.2.8
+ react-dom: 19.2.8(react@19.2.8)
+ optionalDependencies:
+ '@types/react': 19.2.18
+ '@types/react-dom': 19.2.4(@types/react@19.2.18)
- '@protobufjs/float@1.0.2': {}
+ '@radix-ui/react-popover@1.1.23(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.7
+ '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-focus-guards': 1.1.6(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-focus-scope': 1.1.16(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-id': 1.1.4(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-popper': 1.3.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-portal': 1.1.17(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-slot': 1.3.3(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.8)
+ aria-hidden: 1.2.6
+ react: 19.2.8
+ react-dom: 19.2.8(react@19.2.8)
+ react-remove-scroll: 2.7.2(@types/react@19.2.18)(react@19.2.8)
+ optionalDependencies:
+ '@types/react': 19.2.18
+ '@types/react-dom': 19.2.4(@types/react@19.2.18)
- '@protobufjs/path@1.1.2': {}
+ '@radix-ui/react-popper@1.3.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
+ dependencies:
+ '@floating-ui/react-dom': 2.1.8(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-arrow': 1.1.15(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-use-rect': 1.1.4(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-use-size': 1.1.4(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/rect': 1.1.3
+ react: 19.2.8
+ react-dom: 19.2.8(react@19.2.8)
+ optionalDependencies:
+ '@types/react': 19.2.18
+ '@types/react-dom': 19.2.4(@types/react@19.2.18)
- '@protobufjs/pool@1.1.0': {}
+ '@radix-ui/react-portal@1.1.17(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
+ dependencies:
+ '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.8)
+ react: 19.2.8
+ react-dom: 19.2.8(react@19.2.8)
+ optionalDependencies:
+ '@types/react': 19.2.18
+ '@types/react-dom': 19.2.4(@types/react@19.2.18)
- '@protobufjs/utf8@1.1.2': {}
+ '@radix-ui/react-presence@1.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
+ dependencies:
+ '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.8)
+ react: 19.2.8
+ react-dom: 19.2.8(react@19.2.8)
+ optionalDependencies:
+ '@types/react': 19.2.18
+ '@types/react-dom': 19.2.4(@types/react@19.2.18)
- '@radix-ui/primitive@1.1.7': {}
+ '@radix-ui/react-primitive@2.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
+ dependencies:
+ '@radix-ui/react-slot': 1.3.3(@types/react@19.2.18)(react@19.2.8)
+ react: 19.2.8
+ react-dom: 19.2.8(react@19.2.8)
+ optionalDependencies:
+ '@types/react': 19.2.18
+ '@types/react-dom': 19.2.4(@types/react@19.2.18)
- '@radix-ui/react-avatar@1.1.11(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
+ '@radix-ui/react-primitive@2.1.4(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
dependencies:
- '@radix-ui/react-context': 1.1.3(@types/react@19.2.18)(react@19.2.8)
- '@radix-ui/react-primitive': 2.1.4(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
- '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.18)(react@19.2.8)
- '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.2.18)(react@19.2.8)
- '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-slot': 1.2.4(@types/react@19.2.18)(react@19.2.8)
react: 19.2.8
react-dom: 19.2.8(react@19.2.8)
optionalDependencies:
'@types/react': 19.2.18
'@types/react-dom': 19.2.4(@types/react@19.2.18)
- '@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.18)(react@19.2.8)':
+ '@radix-ui/react-progress@1.1.16(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
dependencies:
+ '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
react: 19.2.8
+ react-dom: 19.2.8(react@19.2.8)
optionalDependencies:
'@types/react': 19.2.18
+ '@types/react-dom': 19.2.4(@types/react@19.2.18)
- '@radix-ui/react-compose-refs@1.1.5(@types/react@19.2.18)(react@19.2.8)':
+ '@radix-ui/react-radio-group@1.4.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
dependencies:
+ '@radix-ui/primitive': 1.1.7
+ '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-direction': 1.1.4(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-roving-focus': 1.1.19(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-use-size': 1.1.4(@types/react@19.2.18)(react@19.2.8)
react: 19.2.8
+ react-dom: 19.2.8(react@19.2.8)
optionalDependencies:
'@types/react': 19.2.18
+ '@types/react-dom': 19.2.4(@types/react@19.2.18)
- '@radix-ui/react-context@1.1.3(@types/react@19.2.18)(react@19.2.8)':
+ '@radix-ui/react-roving-focus@1.1.19(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
dependencies:
+ '@radix-ui/primitive': 1.1.7
+ '@radix-ui/react-collection': 1.1.15(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-direction': 1.1.4(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-id': 1.1.4(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-use-is-hydrated': 0.1.3(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.8)
react: 19.2.8
+ react-dom: 19.2.8(react@19.2.8)
optionalDependencies:
'@types/react': 19.2.18
+ '@types/react-dom': 19.2.4(@types/react@19.2.18)
- '@radix-ui/react-context@1.2.2(@types/react@19.2.18)(react@19.2.8)':
+ '@radix-ui/react-scroll-area@1.2.18(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
dependencies:
+ '@radix-ui/number': 1.1.3
+ '@radix-ui/primitive': 1.1.7
+ '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-direction': 1.1.4(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.8)
react: 19.2.8
+ react-dom: 19.2.8(react@19.2.8)
optionalDependencies:
'@types/react': 19.2.18
+ '@types/react-dom': 19.2.4(@types/react@19.2.18)
- '@radix-ui/react-dialog@1.1.23(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
+ '@radix-ui/react-select@2.3.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
dependencies:
+ '@radix-ui/number': 1.1.3
'@radix-ui/primitive': 1.1.7
+ '@radix-ui/react-collection': 1.1.15(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
'@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8)
'@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-direction': 1.1.4(@types/react@19.2.18)(react@19.2.8)
'@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
'@radix-ui/react-focus-guards': 1.1.6(@types/react@19.2.18)(react@19.2.8)
'@radix-ui/react-focus-scope': 1.1.16(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
'@radix-ui/react-id': 1.1.4(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-popper': 1.3.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
'@radix-ui/react-portal': 1.1.17(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
'@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
'@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
'@radix-ui/react-slot': 1.3.3(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.18)(react@19.2.8)
'@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.8)
'@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-use-previous': 1.1.4(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-visually-hidden': 1.2.11(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
aria-hidden: 1.2.6
react: 19.2.8
react-dom: 19.2.8(react@19.2.8)
@@ -9527,93 +11045,159 @@ snapshots:
'@types/react': 19.2.18
'@types/react-dom': 19.2.4(@types/react@19.2.18)
- '@radix-ui/react-dismissable-layer@1.1.19(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
+ '@radix-ui/react-separator@1.1.15(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
+ dependencies:
+ '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ react: 19.2.8
+ react-dom: 19.2.8(react@19.2.8)
+ optionalDependencies:
+ '@types/react': 19.2.18
+ '@types/react-dom': 19.2.4(@types/react@19.2.18)
+
+ '@radix-ui/react-slider@1.4.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
dependencies:
+ '@radix-ui/number': 1.1.3
'@radix-ui/primitive': 1.1.7
+ '@radix-ui/react-collection': 1.1.15(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
'@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-direction': 1.1.4(@types/react@19.2.18)(react@19.2.8)
'@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
- '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.18)(react@19.2.8)
- '@radix-ui/react-use-effect-event': 0.0.5(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-use-previous': 1.1.4(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-use-size': 1.1.4(@types/react@19.2.18)(react@19.2.8)
react: 19.2.8
react-dom: 19.2.8(react@19.2.8)
optionalDependencies:
'@types/react': 19.2.18
'@types/react-dom': 19.2.4(@types/react@19.2.18)
- '@radix-ui/react-focus-guards@1.1.6(@types/react@19.2.18)(react@19.2.8)':
+ '@radix-ui/react-slot@1.2.4(@types/react@19.2.18)(react@19.2.8)':
dependencies:
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.18)(react@19.2.8)
react: 19.2.8
optionalDependencies:
'@types/react': 19.2.18
- '@radix-ui/react-focus-scope@1.1.16(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
+ '@radix-ui/react-slot@1.3.3(@types/react@19.2.18)(react@19.2.8)':
dependencies:
'@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8)
- '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
- '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.18)(react@19.2.8)
react: 19.2.8
- react-dom: 19.2.8(react@19.2.8)
optionalDependencies:
'@types/react': 19.2.18
- '@types/react-dom': 19.2.4(@types/react@19.2.18)
- '@radix-ui/react-id@1.1.4(@types/react@19.2.18)(react@19.2.8)':
+ '@radix-ui/react-switch@1.3.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
dependencies:
- '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/primitive': 1.1.7
+ '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-use-size': 1.1.4(@types/react@19.2.18)(react@19.2.8)
react: 19.2.8
+ react-dom: 19.2.8(react@19.2.8)
optionalDependencies:
'@types/react': 19.2.18
+ '@types/react-dom': 19.2.4(@types/react@19.2.18)
- '@radix-ui/react-portal@1.1.17(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
+ '@radix-ui/react-tabs@1.1.21(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
dependencies:
+ '@radix-ui/primitive': 1.1.7
+ '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-direction': 1.1.4(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-id': 1.1.4(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
'@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
- '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-roving-focus': 1.1.19(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.8)
react: 19.2.8
react-dom: 19.2.8(react@19.2.8)
optionalDependencies:
'@types/react': 19.2.18
'@types/react-dom': 19.2.4(@types/react@19.2.18)
- '@radix-ui/react-presence@1.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
+ '@radix-ui/react-toast@1.2.23(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
dependencies:
+ '@radix-ui/primitive': 1.1.7
+ '@radix-ui/react-collection': 1.1.15(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-portal': 1.1.17(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.8)
'@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-visually-hidden': 1.2.11(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
react: 19.2.8
react-dom: 19.2.8(react@19.2.8)
optionalDependencies:
'@types/react': 19.2.18
'@types/react-dom': 19.2.4(@types/react@19.2.18)
- '@radix-ui/react-primitive@2.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
+ '@radix-ui/react-toggle-group@1.1.19(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
dependencies:
- '@radix-ui/react-slot': 1.3.3(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/primitive': 1.1.7
+ '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-direction': 1.1.4(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-roving-focus': 1.1.19(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-toggle': 1.1.18(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.8)
react: 19.2.8
react-dom: 19.2.8(react@19.2.8)
optionalDependencies:
'@types/react': 19.2.18
'@types/react-dom': 19.2.4(@types/react@19.2.18)
- '@radix-ui/react-primitive@2.1.4(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
+ '@radix-ui/react-toggle@1.1.18(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
dependencies:
- '@radix-ui/react-slot': 1.2.4(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/primitive': 1.1.7
+ '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.8)
react: 19.2.8
react-dom: 19.2.8(react@19.2.8)
optionalDependencies:
'@types/react': 19.2.18
'@types/react-dom': 19.2.4(@types/react@19.2.18)
- '@radix-ui/react-slot@1.2.4(@types/react@19.2.18)(react@19.2.8)':
+ '@radix-ui/react-toolbar@1.1.19(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
dependencies:
- '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/primitive': 1.1.7
+ '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-direction': 1.1.4(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-roving-focus': 1.1.19(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-separator': 1.1.15(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-toggle-group': 1.1.19(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
react: 19.2.8
+ react-dom: 19.2.8(react@19.2.8)
optionalDependencies:
'@types/react': 19.2.18
+ '@types/react-dom': 19.2.4(@types/react@19.2.18)
- '@radix-ui/react-slot@1.3.3(@types/react@19.2.18)(react@19.2.8)':
+ '@radix-ui/react-tooltip@1.2.16(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
dependencies:
+ '@radix-ui/primitive': 1.1.7
'@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-id': 1.1.4(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-popper': 1.3.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-portal': 1.1.17(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-slot': 1.3.3(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-visually-hidden': 1.2.11(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
react: 19.2.8
+ react-dom: 19.2.8(react@19.2.8)
optionalDependencies:
'@types/react': 19.2.18
+ '@types/react-dom': 19.2.4(@types/react@19.2.18)
'@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.2.18)(react@19.2.8)':
dependencies:
@@ -9643,6 +11227,13 @@ snapshots:
optionalDependencies:
'@types/react': 19.2.18
+ '@radix-ui/react-use-escape-keydown@1.1.5(@types/react@19.2.18)(react@19.2.8)':
+ dependencies:
+ '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.18)(react@19.2.8)
+ react: 19.2.8
+ optionalDependencies:
+ '@types/react': 19.2.18
+
'@radix-ui/react-use-is-hydrated@0.1.0(@types/react@19.2.18)(react@19.2.8)':
dependencies:
react: 19.2.8
@@ -9650,6 +11241,12 @@ snapshots:
optionalDependencies:
'@types/react': 19.2.18
+ '@radix-ui/react-use-is-hydrated@0.1.3(@types/react@19.2.18)(react@19.2.8)':
+ dependencies:
+ react: 19.2.8
+ optionalDependencies:
+ '@types/react': 19.2.18
+
'@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.2.18)(react@19.2.8)':
dependencies:
react: 19.2.8
@@ -9662,6 +11259,37 @@ snapshots:
optionalDependencies:
'@types/react': 19.2.18
+ '@radix-ui/react-use-previous@1.1.4(@types/react@19.2.18)(react@19.2.8)':
+ dependencies:
+ react: 19.2.8
+ optionalDependencies:
+ '@types/react': 19.2.18
+
+ '@radix-ui/react-use-rect@1.1.4(@types/react@19.2.18)(react@19.2.8)':
+ dependencies:
+ '@radix-ui/rect': 1.1.3
+ react: 19.2.8
+ optionalDependencies:
+ '@types/react': 19.2.18
+
+ '@radix-ui/react-use-size@1.1.4(@types/react@19.2.18)(react@19.2.8)':
+ dependencies:
+ '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.8)
+ react: 19.2.8
+ optionalDependencies:
+ '@types/react': 19.2.18
+
+ '@radix-ui/react-visually-hidden@1.2.11(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
+ dependencies:
+ '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ react: 19.2.8
+ react-dom: 19.2.8(react@19.2.8)
+ optionalDependencies:
+ '@types/react': 19.2.18
+ '@types/react-dom': 19.2.4(@types/react@19.2.18)
+
+ '@radix-ui/rect@1.1.3': {}
+
'@react-aria/color@3.2.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
dependencies:
'@swc/helpers': 0.5.23
@@ -10343,6 +11971,8 @@ snapshots:
dependencies:
undici-types: 8.3.0
+ '@types/prismjs@1.26.6': {}
+
'@types/react-dom@19.2.4(@types/react@19.2.18)':
dependencies:
'@types/react': 19.2.18
@@ -10969,6 +12599,8 @@ snapshots:
convert-source-map: 2.0.0
tinyrainbow: 3.1.1
+ '@workflow/serde@4.1.0': {}
+
'@workflow/serde@4.1.0-beta.2': {}
'@xmldom/xmldom@0.9.10': {}
@@ -11014,6 +12646,13 @@ snapshots:
agent-base@7.1.4: {}
+ ai@7.0.77(zod@4.4.3):
+ dependencies:
+ '@ai-sdk/gateway': 4.0.62(zod@4.4.3)
+ '@ai-sdk/provider': 4.0.7
+ '@ai-sdk/provider-utils': 5.0.29(zod@4.4.3)
+ zod: 4.4.3
+
ajv-formats@2.1.1(ajv@8.20.0):
optionalDependencies:
ajv: 8.20.0
@@ -11180,6 +12819,19 @@ snapshots:
assertion-error@2.0.1: {}
+ assistant-cloud@0.1.41:
+ dependencies:
+ assistant-stream: 0.3.39
+ transitivePeerDependencies:
+ - ioredis
+ - redis
+
+ assistant-stream@0.3.39:
+ dependencies:
+ '@standard-schema/spec': 1.1.0
+ nanoid: 6.0.1
+ secure-json-parse: 4.1.0
+
ast-types-flow@0.0.8: {}
ast-types@0.13.4:
@@ -11446,6 +13098,8 @@ snapshots:
dependencies:
json-parse-helpfulerror: 1.0.3
+ classnames@2.5.1: {}
+
cli-boxes@2.2.1: {}
cli-cursor@3.1.0:
@@ -12709,6 +14363,10 @@ snapshots:
dependencies:
reusify: 1.1.0
+ fault@1.0.4:
+ dependencies:
+ format: 0.2.2
+
fault@2.0.1:
dependencies:
format: 0.2.2
@@ -13382,6 +15040,8 @@ snapshots:
highlight.js@10.7.3: {}
+ highlightjs-vue@1.0.0: {}
+
hono@4.13.3: {}
html-encoding-sniffer@6.0.0:
@@ -13392,6 +15052,8 @@ snapshots:
html-escaper@2.0.2: {}
+ html-url-attributes@3.0.1: {}
+
html-void-elements@3.0.0: {}
http-errors@1.7.3:
@@ -13825,6 +15487,8 @@ snapshots:
json-schema-typed@8.0.2: {}
+ json-schema@0.4.0: {}
+
json-stable-stringify-without-jsonify@1.0.1: {}
json5@1.0.2:
@@ -14063,6 +15727,11 @@ snapshots:
dependencies:
js-tokens: 4.0.0
+ lowlight@1.20.0:
+ dependencies:
+ fault: 1.0.4
+ highlight.js: 10.7.3
+
lru-cache@10.4.3: {}
lru-cache@11.5.2: {}
@@ -14735,6 +16404,8 @@ snapshots:
nanoid@3.3.18: {}
+ nanoid@6.0.1: {}
+
napi-postinstall@0.3.4: {}
natural-compare@1.4.0: {}
@@ -15307,6 +16978,8 @@ snapshots:
dependencies:
parse-ms: 2.1.0
+ prismjs@1.30.0: {}
+
proc-log@7.0.0:
optional: true
@@ -15386,6 +17059,69 @@ snapshots:
queue-microtask@1.2.3: {}
+ radix-ui@1.6.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8):
+ dependencies:
+ '@radix-ui/primitive': 1.1.7
+ '@radix-ui/react-accessible-icon': 1.1.15(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-accordion': 1.2.20(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-alert-dialog': 1.1.23(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-arrow': 1.1.15(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-aspect-ratio': 1.1.15(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-avatar': 1.2.6(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-checkbox': 1.3.11(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-collapsible': 1.1.20(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-collection': 1.1.15(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-context-menu': 2.3.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-dialog': 1.1.23(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-direction': 1.1.4(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-dropdown-menu': 2.1.24(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-focus-guards': 1.1.6(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-focus-scope': 1.1.16(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-form': 0.1.16(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-hover-card': 1.1.23(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-label': 2.1.15(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-menu': 2.1.24(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-menubar': 1.1.24(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-navigation-menu': 1.2.22(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-one-time-password-field': 0.1.16(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-password-toggle-field': 0.1.11(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-popover': 1.1.23(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-popper': 1.3.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-portal': 1.1.17(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-progress': 1.1.16(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-radio-group': 1.4.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-roving-focus': 1.1.19(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-scroll-area': 1.2.18(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-select': 2.3.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-separator': 1.1.15(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-slider': 1.4.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-slot': 1.3.3(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-switch': 1.3.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-tabs': 1.1.21(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-toast': 1.2.23(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-toggle': 1.1.18(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-toggle-group': 1.1.19(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-toolbar': 1.1.19(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-tooltip': 1.2.16(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-use-effect-event': 0.0.5(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-use-escape-keydown': 1.1.5(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-use-is-hydrated': 0.1.3(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-use-size': 1.1.4(@types/react@19.2.18)(react@19.2.8)
+ '@radix-ui/react-visually-hidden': 1.2.11(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ react: 19.2.8
+ react-dom: 19.2.8(react@19.2.8)
+ optionalDependencies:
+ '@types/react': 19.2.18
+ '@types/react-dom': 19.2.4(@types/react@19.2.18)
+
railroad-diagrams@1.0.0: {}
randexp@0.4.6:
@@ -15510,6 +17246,24 @@ snapshots:
react-is@17.0.2: {}
+ react-markdown@10.1.0(@types/react@19.2.18)(react@19.2.8)(supports-color@7.2.0):
+ dependencies:
+ '@types/hast': 3.0.4
+ '@types/mdast': 4.0.4
+ '@types/react': 19.2.18
+ devlop: 1.1.0
+ hast-util-to-jsx-runtime: 2.3.6(supports-color@7.2.0)
+ html-url-attributes: 3.0.1
+ mdast-util-to-hast: 13.2.1
+ react: 19.2.8
+ remark-parse: 11.0.0(supports-color@7.2.0)
+ remark-rehype: 11.1.2
+ unified: 11.0.5
+ unist-util-visit: 5.1.0
+ vfile: 6.0.3
+ transitivePeerDependencies:
+ - supports-color
+
react-medium-image-zoom@5.4.8(react-dom@19.2.8(react@19.2.8))(react@19.2.8):
dependencies:
react: 19.2.8
@@ -15572,6 +17326,25 @@ snapshots:
optionalDependencies:
'@types/react': 19.2.18
+ react-syntax-highlighter@16.1.1(react@19.2.8):
+ dependencies:
+ '@babel/runtime': 7.29.7
+ highlight.js: 10.7.3
+ highlightjs-vue: 1.0.0
+ lowlight: 1.20.0
+ prismjs: 1.30.0
+ react: 19.2.8
+ refractor: 5.0.0
+
+ react-textarea-autosize@8.5.9(@types/react@19.2.18)(react@19.2.8):
+ dependencies:
+ '@babel/runtime': 7.29.7
+ react: 19.2.8
+ use-composed-ref: 1.4.0(@types/react@19.2.18)(react@19.2.8)
+ use-latest: 1.3.0(@types/react@19.2.18)(react@19.2.8)
+ transitivePeerDependencies:
+ - '@types/react'
+
react-transition-group@4.4.5(react-dom@19.2.8(react@19.2.8))(react@19.2.8):
dependencies:
'@babel/runtime': 7.29.7
@@ -15664,6 +17437,13 @@ snapshots:
get-proto: 1.0.1
which-builtin-type: 1.2.1
+ refractor@5.0.0:
+ dependencies:
+ '@types/hast': 3.0.4
+ '@types/prismjs': 1.26.6
+ hastscript: 9.0.1
+ parse-entities: 4.0.2
+
regex-recursion@6.0.2:
dependencies:
regex-utilities: 2.3.0
@@ -15984,6 +17764,8 @@ snapshots:
safe-buffer@5.2.1: {}
+ safe-content-frame@0.0.27: {}
+
safe-push-apply@1.0.0:
dependencies:
es-errors: 1.3.0
@@ -16023,6 +17805,8 @@ snapshots:
dependencies:
compute-scroll-into-view: 3.1.1
+ secure-json-parse@4.1.0: {}
+
semver-diff@3.1.1:
dependencies:
semver: 6.3.1
@@ -16460,6 +18244,12 @@ snapshots:
supports-preserve-symlinks-flag@1.0.0: {}
+ swr@2.5.1(react@19.2.8):
+ dependencies:
+ dequal: 2.0.3
+ react: 19.2.8
+ use-sync-external-store: 1.6.0(react@19.2.8)
+
symbol-tree@3.2.4: {}
system-architecture@0.1.0: {}
@@ -16895,6 +18685,25 @@ snapshots:
optionalDependencies:
'@types/react': 19.2.18
+ use-composed-ref@1.4.0(@types/react@19.2.18)(react@19.2.8):
+ dependencies:
+ react: 19.2.8
+ optionalDependencies:
+ '@types/react': 19.2.18
+
+ use-isomorphic-layout-effect@1.2.1(@types/react@19.2.18)(react@19.2.8):
+ dependencies:
+ react: 19.2.8
+ optionalDependencies:
+ '@types/react': 19.2.18
+
+ use-latest@1.3.0(@types/react@19.2.18)(react@19.2.8):
+ dependencies:
+ react: 19.2.8
+ use-isomorphic-layout-effect: 1.2.1(@types/react@19.2.18)(react@19.2.8)
+ optionalDependencies:
+ '@types/react': 19.2.18
+
use-sidecar@1.1.3(@types/react@19.2.18)(react@19.2.8):
dependencies:
detect-node-es: 1.1.0
@@ -17308,4 +19117,10 @@ snapshots:
react: 19.2.8
use-sync-external-store: 1.6.0(react@19.2.8)
+ zustand@5.0.15(@types/react@19.2.18)(react@19.2.8)(use-sync-external-store@1.6.0(react@19.2.8)):
+ optionalDependencies:
+ '@types/react': 19.2.18
+ react: 19.2.8
+ use-sync-external-store: 1.6.0(react@19.2.8)
+
zwitch@2.0.4: {}
diff --git a/scripts/init.ts b/scripts/init.ts
index 6a8289f..73443d7 100644
--- a/scripts/init.ts
+++ b/scripts/init.ts
@@ -90,6 +90,10 @@ async function main() {
'LICENSE',
'CHANGELOG.md',
'app/components/Navbar/index.tsx',
+ 'app/components/AIAssistant/AssistantTrigger.tsx',
+ 'app/components/AIAssistant/Thread.tsx',
+ 'app/lib/ai/system-prompt.ts',
+ 'app/lib/ai/mock-provider.ts',
'app/page.tsx',
'.firebaserc',
'render.yaml',
diff --git a/tests/e2e/ai-assistant.spec.ts b/tests/e2e/ai-assistant.spec.ts
new file mode 100644
index 0000000..28735a5
--- /dev/null
+++ b/tests/e2e/ai-assistant.spec.ts
@@ -0,0 +1,57 @@
+import { test, expect } from "@playwright/test";
+import AxeBuilder from "@axe-core/playwright";
+
+test.describe("AI Assistant E2E & Accessibility", () => {
+ test("should show floating Ask AI trigger on landing page", async ({ page }) => {
+ await page.goto("/");
+ const trigger = page.getByRole("button", { name: /open ai assistant/i }).last();
+ await expect(trigger).toBeVisible();
+ await expect(trigger).toContainText("Ask AI");
+ });
+
+ test("should open modal on clicking floating trigger and close on close button", async ({ page }) => {
+ await page.goto("/");
+ const trigger = page.getByRole("button", { name: /open ai assistant/i }).last();
+ await trigger.click();
+
+ const dialog = page.getByRole("dialog", { name: /ai assistant chat/i });
+ await expect(dialog).toBeVisible();
+ await expect(page.getByText("cur8d Copilot")).toBeVisible();
+
+ const closeBtn = page.getByRole("button", { name: /close ai assistant/i });
+ await closeBtn.click();
+ await expect(dialog).not.toBeVisible();
+ });
+
+ test("should open modal on clicking navbar Ask AI button", async ({ page }) => {
+ await page.goto("/");
+ const navBtn = page.locator("header").getByRole("button", { name: /open ai assistant/i });
+ await navBtn.click();
+
+ const dialog = page.getByRole("dialog", { name: /ai assistant chat/i });
+ await expect(dialog).toBeVisible();
+ });
+
+ test("should toggle modal on pressing keyboard shortcut and close on Escape", async ({ page }) => {
+ await page.goto("/");
+ await page.keyboard.press("ControlOrMeta+j");
+
+ const dialog = page.getByRole("dialog", { name: /ai assistant chat/i });
+ await expect(dialog).toBeVisible();
+
+ await page.keyboard.press("Escape");
+ await expect(dialog).not.toBeVisible();
+ });
+
+ test("AI assistant modal should pass axe-core accessibility audit", async ({ page }) => {
+ await page.goto("/");
+ const trigger = page.getByRole("button", { name: /open ai assistant/i }).last();
+ await trigger.click();
+
+ const dialog = page.getByRole("dialog", { name: /ai assistant chat/i });
+ await expect(dialog).toBeVisible();
+
+ const results = await new AxeBuilder({ page }).analyze();
+ expect(results.violations).toEqual([]);
+ });
+});
diff --git a/tests/unit/api/chat.route.test.ts b/tests/unit/api/chat.route.test.ts
new file mode 100644
index 0000000..22c01dd
--- /dev/null
+++ b/tests/unit/api/chat.route.test.ts
@@ -0,0 +1,127 @@
+import { describe, it, expect, vi, beforeEach } from "vitest";
+import { POST } from "@/api/chat/route";
+import * as errorReporting from "@/lib/error-reporting";
+
+describe("POST /api/chat", () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ });
+
+ it("should return a 200 SSE stream for valid messages", async () => {
+ const req = new Request("http://localhost:3000/api/chat", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({
+ messages: [
+ {
+ id: "msg_1",
+ role: "user",
+ parts: [{ type: "text", text: "Hello AI" }],
+ },
+ ],
+ }),
+ });
+
+ const res = await POST(req);
+ expect(res.status).toBe(200);
+ expect(res.headers.get("content-type")).toContain("text/event-stream");
+ });
+
+ it("should normalize messages with string content and handle missing id/role", async () => {
+ const req = new Request("http://localhost:3000/api/chat", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({
+ messages: [
+ { role: "user", content: "Tell me about cur8d" }, // without id
+ { id: "custom_id", content: "Without role" }, // without role (defaults to user)
+ { role: "assistant", parts: [{ type: "text", text: "Existing parts" }] }, // with parts
+ ],
+ }),
+ });
+
+ const res = await POST(req);
+ expect(res.status).toBe(200);
+ expect(res.headers.get("content-type")).toContain("text/event-stream");
+ });
+
+ it("should handle provider and model overrides", async () => {
+ const req = new Request("http://localhost:3000/api/chat", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({
+ provider: "mock",
+ model: "mock-custom-model",
+ messages: [{ role: "user", content: "Hello" }],
+ }),
+ });
+
+ const res = await POST(req);
+ expect(res.status).toBe(200);
+ });
+
+ it("should return 400 if messages is missing or not an array", async () => {
+ const req = new Request("http://localhost:3000/api/chat", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({}),
+ });
+
+ const res = await POST(req);
+ expect(res.status).toBe(400);
+
+ const json = await res.json();
+ expect(json.error).toContain("Missing or invalid 'messages'");
+ });
+
+ it("should catch errors, report them, and return 500 when request body contains invalid message structure", async () => {
+ const reportErrorSpy = vi.spyOn(errorReporting, "reportError");
+
+ const req = new Request("http://localhost:3000/api/chat", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({
+ messages: [12345], // primitive value that fails convertToModelMessages
+ }),
+ });
+
+ const res = await POST(req);
+ expect(res.status).toBe(500);
+ expect(reportErrorSpy).toHaveBeenCalled();
+
+ const json = await res.json();
+ expect(json.error).toBe("Failed to process chat request");
+ });
+
+ it("should catch JSON parse errors, report them, and return 500", async () => {
+ const reportErrorSpy = vi.spyOn(errorReporting, "reportError");
+
+ const req = new Request("http://localhost:3000/api/chat", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: "invalid-json",
+ });
+
+ const res = await POST(req);
+ expect(res.status).toBe(500);
+ expect(reportErrorSpy).toHaveBeenCalled();
+
+ const json = await res.json();
+ expect(json.error).toBe("Failed to process chat request");
+ });
+
+ it("should format unknown error when non-Error exception occurs", async () => {
+ const reportErrorSpy = vi.spyOn(errorReporting, "reportError");
+
+ const fakeReq = {
+ json: () => Promise.reject("string error reason"),
+ } as unknown as Request;
+
+ const res = await POST(fakeReq);
+ expect(res.status).toBe(500);
+ expect(reportErrorSpy).toHaveBeenCalled();
+
+ const json = await res.json();
+ expect(json.message).toBe("Unknown error");
+ });
+});
diff --git a/tests/unit/components/AIAssistant/index.test.tsx b/tests/unit/components/AIAssistant/index.test.tsx
new file mode 100644
index 0000000..101920f
--- /dev/null
+++ b/tests/unit/components/AIAssistant/index.test.tsx
@@ -0,0 +1,693 @@
+import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
+import { render, screen, fireEvent, act } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+import {
+ AIAssistant,
+ useAIAssistant,
+ AIAssistantProvider,
+ AssistantTrigger,
+ Composer,
+ Thread,
+ assistantToolkit,
+} from "@/components/AIAssistant";
+import {
+ CodeBlock,
+ SuggestedPrompts,
+ MarkdownCode,
+ MarkdownParagraph,
+ MarkdownUnorderedList,
+ MarkdownOrderedList,
+ MarkdownListItem,
+ MarkdownLink,
+} from "@/components/AIAssistant/Thread";
+import { DocSearchTool, type DocSearchArgs } from "@/components/AIAssistant/tools/DocSearchTool";
+import { ThemeTool, type ThemeArgs } from "@/components/AIAssistant/tools/ThemeTool";
+import { SystemInfoTool } from "@/components/AIAssistant/tools/SystemInfoTool";
+import { NavigatePageTool } from "@/components/AIAssistant/tools/NavigatePageTool";
+import * as speechHook from "@/hooks/use-speech-to-text";
+
+const mockPush = vi.fn();
+const mockSetTheme = vi.fn();
+
+vi.mock("next/navigation", () => ({
+ useRouter: () => ({
+ push: mockPush,
+ replace: vi.fn(),
+ prefetch: vi.fn(),
+ }),
+}));
+
+vi.mock("next-themes", () => ({
+ useTheme: vi.fn(() => ({
+ theme: "dark",
+ setTheme: mockSetTheme,
+ })),
+ ThemeProvider: ({ children }: { children: React.ReactNode }) => <>{children}>,
+}));
+
+vi.mock("@assistant-ui/react-ai-sdk", () => ({
+ useChatRuntime: vi.fn(() => ({})),
+ AssistantChatTransport: vi.fn(),
+}));
+
+vi.mock("@assistant-ui/react", () => ({
+ AssistantRuntimeProvider: ({ children }: { children: React.ReactNode }) => <>{children}>,
+ AuiConfig: (config: unknown) => config,
+ Tools: (tools: unknown) => tools,
+ defineToolkit: (toolkit: T): T => toolkit,
+ ThreadPrimitive: {
+ Root: ({ children, className }: { children?: React.ReactNode; className?: string }) => (
+
+ {children}
+
+ ),
+ Viewport: ({ children, className }: { children?: React.ReactNode; className?: string }) => (
+
+ {children}
+
+ ),
+ Suggestion: ({ children, prompt }: { children?: React.ReactNode; prompt?: string }) => (
+ {children}
+ ),
+ Messages: () => ,
+ },
+ ComposerPrimitive: {
+ Root: ({ children, className }: { children?: React.ReactNode; className?: string }) => (
+
+ ),
+ Input: ({ placeholder, autoFocus, className }: { placeholder?: string; autoFocus?: boolean; className?: string }) => (
+
+ ),
+ Cancel: ({ children }: { children?: React.ReactNode; asChild?: boolean }) => <>{children}>,
+ Send: ({ children }: { children?: React.ReactNode; asChild?: boolean }) => <>{children}>,
+ },
+ MessagePrimitive: {
+ Root: ({ children, className }: { children?: React.ReactNode; className?: string }) => (
+
+ {children}
+
+ ),
+ Content: () => ,
+ },
+ BranchPickerPrimitive: {
+ Root: ({ children }: { children?: React.ReactNode }) => {children}
,
+ Previous: ({ children }: { children?: React.ReactNode }) => <>{children}>,
+ Next: ({ children }: { children?: React.ReactNode }) => <>{children}>,
+ Number: () => 1,
+ Count: () => 1,
+ },
+ ActionBarPrimitive: {
+ Root: ({ children }: { children?: React.ReactNode }) => {children}
,
+ Copy: ({ children }: { children?: React.ReactNode }) => <>{children}>,
+ Reload: ({ children }: { children?: React.ReactNode }) => <>{children}>,
+ },
+ AuiIf: ({
+ children,
+ condition,
+ }: {
+ children?: React.ReactNode;
+ condition?: (state: { thread: { isEmpty: boolean } }) => boolean;
+ }) => {
+ condition?.({ thread: { isEmpty: true } });
+ return <>{children}>;
+ },
+}));
+
+vi.mock("@assistant-ui/react-markdown", () => ({
+ MarkdownTextPrimitive: () => ,
+}));
+
+function AssistantTestWrapper({ children }: { readonly children: React.ReactNode }) {
+ return <>{children}>;
+}
+
+function TestConsumer() {
+ const { isOpen, toggle } = useAIAssistant();
+ return (
+
+
{isOpen ? "open" : "closed"}
+
+
+ );
+}
+
+describe("AIAssistant Components", () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ });
+
+ describe("AIAssistantProvider & AssistantTrigger", () => {
+ it("should render floating trigger button with Mac platform shortcut", () => {
+ render(
+
+
+
+ );
+ const trigger = screen.getByRole("button", { name: /open ai assistant/i });
+ expect(trigger).toBeInTheDocument();
+ expect(screen.getByText("Ask AI")).toBeInTheDocument();
+ });
+
+ it("should render Ctrl+J shortcut on non-Mac platforms", () => {
+ const originalPlatform = navigator.platform;
+ try {
+ Object.defineProperty(navigator, "platform", {
+ value: "Win32",
+ configurable: true,
+ });
+
+ render(
+
+
+
+ );
+
+ expect(screen.getByText("Ctrl+J")).toBeInTheDocument();
+
+ fireEvent.click(screen.getByRole("button", { name: /open ai assistant/i }));
+ expect(screen.getAllByText("Ctrl+J").length).toBeGreaterThan(0);
+ } finally {
+ Object.defineProperty(navigator, "platform", {
+ value: originalPlatform,
+ configurable: true,
+ });
+ }
+ });
+
+ it("should open and close the modal when trigger is clicked", async () => {
+ const user = userEvent.setup();
+ render(
+
+
+
+ );
+
+ const trigger = screen.getByRole("button", { name: /open ai assistant/i });
+ await user.click(trigger);
+
+ expect(screen.getByRole("dialog", { name: /ai assistant chat/i })).toBeInTheDocument();
+ expect(screen.getByText("cur8d Copilot")).toBeInTheDocument();
+
+ const closeButton = screen.getByRole("button", { name: /close ai assistant/i });
+ await user.click(closeButton);
+
+ expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
+ });
+
+ it("should toggle open state with Command/Ctrl + J shortcut and Escape key", () => {
+ render(
+
+
+
+ );
+
+ fireEvent.keyDown(window, { key: "j", metaKey: true });
+ expect(screen.getByRole("dialog")).toBeInTheDocument();
+
+ fireEvent.keyDown(window, { key: "Escape" });
+ expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
+ });
+
+ it("should close when backdrop is clicked", () => {
+ render(
+
+
+
+ );
+
+ fireEvent.keyDown(window, { key: "j", metaKey: true });
+ expect(screen.getByRole("dialog")).toBeInTheDocument();
+
+ const backdrop = document.querySelector(".bg-black\\/40");
+ expect(backdrop).toBeInTheDocument();
+ if (backdrop) {
+ fireEvent.click(backdrop);
+ }
+ expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
+ });
+
+ it("should provide safe fallback if useAIAssistant is used outside provider", () => {
+ render();
+ expect(screen.getByTestId("status")).toHaveTextContent("closed");
+ fireEvent.click(screen.getByText("Toggle"));
+ expect(screen.getByTestId("status")).toHaveTextContent("closed");
+ });
+
+ it("should work standalone without AIAssistantProvider", () => {
+ render(
+
+
+
+ );
+
+ const trigger = screen.getByRole("button", { name: /open ai assistant/i });
+ fireEvent.click(trigger);
+ expect(screen.getByRole("dialog")).toBeInTheDocument();
+
+ fireEvent.keyDown(window, { key: "Escape" });
+ expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
+
+ // Standalone keyboard shortcut
+ fireEvent.keyDown(window, { key: "j", ctrlKey: true });
+ expect(screen.getByRole("dialog")).toBeInTheDocument();
+ });
+ });
+
+ describe("Composer Component", () => {
+ afterEach(() => {
+ vi.restoreAllMocks();
+ });
+
+ it("should render default composer when speech is unsupported", () => {
+ vi.spyOn(speechHook, "useSpeechToText").mockReturnValue({
+ isListening: false,
+ transcript: "",
+ isSupported: false,
+ error: null,
+ startListening: vi.fn(),
+ stopListening: vi.fn(),
+ toggleListening: vi.fn(),
+ resetTranscript: vi.fn(),
+ });
+
+ render(
+
+
+
+ );
+ expect(screen.getByPlaceholderText(/Ask cur8d assistant/i)).toBeInTheDocument();
+ expect(screen.queryByRole("button", { name: /voice input/i })).not.toBeInTheDocument();
+ expect(screen.getByRole("button", { name: /send message/i })).toBeInTheDocument();
+ expect(screen.getByRole("button", { name: /cancel generation/i })).toBeInTheDocument();
+ });
+
+ it("should render mic button and toggle speech when supported", () => {
+ const toggleListening = vi.fn();
+ vi.spyOn(speechHook, "useSpeechToText").mockReturnValue({
+ isListening: false,
+ transcript: "",
+ isSupported: true,
+ error: null,
+ startListening: vi.fn(),
+ stopListening: vi.fn(),
+ toggleListening,
+ resetTranscript: vi.fn(),
+ });
+
+ render(
+
+
+
+ );
+ const micButton = screen.getByRole("button", { name: /start voice input/i });
+ expect(micButton).toBeInTheDocument();
+
+ fireEvent.click(micButton);
+ expect(toggleListening).toHaveBeenCalled();
+ });
+
+ it("should render listening indicator when speech recognition is active", () => {
+ const toggleListening = vi.fn();
+ vi.spyOn(speechHook, "useSpeechToText").mockReturnValue({
+ isListening: true,
+ transcript: "speech test",
+ isSupported: true,
+ error: null,
+ startListening: vi.fn(),
+ stopListening: vi.fn(),
+ toggleListening,
+ resetTranscript: vi.fn(),
+ });
+
+ render(
+
+
+
+ );
+ expect(screen.getByRole("button", { name: /stop listening/i })).toBeInTheDocument();
+ expect(screen.getByText("Listening...")).toBeInTheDocument();
+ });
+ });
+
+ describe("Thread and Markdown Components", () => {
+ describe("CodeBlock", () => {
+ it("should render code and language header and copy to clipboard", async () => {
+ vi.useFakeTimers();
+ const writeTextMock = vi.fn().mockResolvedValue(undefined);
+ Object.defineProperty(navigator, "clipboard", {
+ value: {
+ writeText: writeTextMock,
+ },
+ configurable: true,
+ writable: true,
+ });
+
+ render();
+ expect(screen.getByText("typescript")).toBeInTheDocument();
+ expect(screen.getByText("const a = 1;")).toBeInTheDocument();
+
+ const copyButton = screen.getByRole("button", { name: /copy code block/i });
+ expect(copyButton).toBeInTheDocument();
+
+ fireEvent.click(copyButton);
+ await act(async () => { });
+ expect(writeTextMock).toHaveBeenCalledWith("const a = 1;");
+ expect(screen.getByText("Copied")).toBeInTheDocument();
+
+ act(() => {
+ vi.advanceTimersByTime(2100);
+ });
+ expect(screen.getByText("Copy")).toBeInTheDocument();
+
+ vi.useRealTimers();
+ });
+
+ it("should fallback gracefully if clipboard write fails", async () => {
+ Object.defineProperty(navigator, "clipboard", {
+ value: {
+ writeText: vi.fn().mockRejectedValue(new Error("Permission denied")),
+ },
+ configurable: true,
+ writable: true,
+ });
+
+ render();
+ expect(screen.getByText("text")).toBeInTheDocument();
+
+ const copyButton = screen.getByRole("button", { name: /copy code block/i });
+ fireEvent.click(copyButton);
+ expect(screen.getByText("Copy")).toBeInTheDocument();
+ });
+ });
+
+ describe("SuggestedPrompts", () => {
+ it("should render prompt buttons", () => {
+ render(
+
+
+
+ );
+ expect(screen.getByText("How can I assist you today?")).toBeInTheDocument();
+ expect(screen.getByText("Search documentation")).toBeInTheDocument();
+ expect(screen.getByText("Switch to dark mode")).toBeInTheDocument();
+ expect(screen.getByText("Show system info")).toBeInTheDocument();
+ expect(screen.getByText("How to test")).toBeInTheDocument();
+ });
+ });
+
+ describe("Markdown components", () => {
+ it("should render MarkdownCode inline and block variants", () => {
+ const { getByText, rerender } = render(
+ const x = 10;
+ );
+ expect(getByText("const x = 10;").tagName).toBe("CODE");
+
+ rerender(
+
+ {"function test() {\n return 1;\n}"}
+
+ );
+ expect(getByText("javascript")).toBeInTheDocument();
+ expect(getByText(/function test/)).toBeInTheDocument();
+
+ // Children as array
+ rerender(
+
+ {["body", " { ", "margin: 0;", " }"]}
+
+ );
+ expect(getByText("css")).toBeInTheDocument();
+ });
+
+ it("should render MarkdownParagraph, Lists, and Links", () => {
+ const { getByText, container } = render(
+
+ Paragraph text
+
+ Bullet 1
+
+
+ Ordered 1
+
+ cur8d Link
+
+ );
+
+ expect(getByText("Paragraph text")).toBeInTheDocument();
+ expect(getByText("Bullet 1")).toBeInTheDocument();
+ expect(getByText("Ordered 1")).toBeInTheDocument();
+ const link = getByText("cur8d Link");
+ expect(link).toHaveAttribute("href", "https://cur8d.dev");
+ expect(link).toHaveAttribute("target", "_blank");
+ expect(link).toHaveAttribute("rel", "noopener noreferrer");
+ expect(container.querySelector("ul")).toBeInTheDocument();
+ expect(container.querySelector("ol")).toBeInTheDocument();
+ });
+ });
+
+ describe("Thread component", () => {
+ it("should render thread root and composer", () => {
+ const { container } = render(
+
+
+
+ );
+ expect(container).toBeInTheDocument();
+ });
+ });
+ });
+
+ describe("Generative UI Tools", () => {
+ const defaultToolProps = {
+ type: "tool-call" as const,
+ addResult: vi.fn(),
+ resume: vi.fn(),
+ respondToApproval: vi.fn(),
+ toolCallId: "tool-call-1",
+ argsText: "",
+ };
+
+ it("should export assistantToolkit with all tool renderers configured", () => {
+ expect(assistantToolkit).toBeDefined();
+ expect(assistantToolkit.searchDocumentation.render).toBe(DocSearchTool);
+ expect(assistantToolkit.setTheme.render).toBe(ThemeTool);
+ expect(assistantToolkit.getSystemInfo.render).toBe(SystemInfoTool);
+ expect(assistantToolkit.navigatePage.render).toBe(NavigatePageTool);
+ });
+
+ describe("DocSearchTool", () => {
+ it("should render running state with query and default topics fallback", () => {
+ const { getByText, rerender } = render(
+
+ );
+ expect(getByText(/Searching documentation for "getting started"/i)).toBeInTheDocument();
+
+ rerender(
+
+ );
+ expect(getByText(/Searching documentation for "topics"/i)).toBeInTheDocument();
+ });
+
+ it("should render empty state when no results", () => {
+ const { getByText } = render(
+
+ );
+ expect(getByText(/No documentation matches found/i)).toBeInTheDocument();
+ });
+
+ it("should render document search result cards with links", () => {
+ const { getByText } = render(
+
+ );
+ expect(getByText("Project Overview")).toBeInTheDocument();
+ expect(getByText("Learn about cur8d architecture")).toBeInTheDocument();
+ expect(getByText("Getting Started")).toBeInTheDocument();
+ });
+ });
+
+ describe("ThemeTool", () => {
+ it("should render running and completed states for dark, light, and system themes", () => {
+ // Dark theme running
+ const { getByText, rerender } = render(
+
+ );
+ expect(getByText(/Switching theme to dark/i)).toBeInTheDocument();
+
+ // Light theme running
+ rerender(
+
+ );
+ expect(getByText(/Switching theme to light/i)).toBeInTheDocument();
+
+ // System theme running with undefined args fallback
+ rerender(
+
+ );
+ expect(getByText(/Switching theme to system/i)).toBeInTheDocument();
+
+ // Completed light theme
+ rerender(
+
+ );
+ expect(getByText(/Light applied/i)).toBeInTheDocument();
+ expect(mockSetTheme).toHaveBeenCalledWith("light");
+
+ // Completed dark theme
+ rerender(
+
+ );
+ expect(getByText(/Dark applied/i)).toBeInTheDocument();
+
+ // Completed system theme
+ rerender(
+
+ );
+ expect(getByText(/System applied/i)).toBeInTheDocument();
+ });
+ });
+
+ describe("SystemInfoTool", () => {
+ it("should render running state", () => {
+ const { getByText } = render(
+
+ );
+ expect(getByText(/Querying system metrics/i)).toBeInTheDocument();
+ });
+
+ it("should return null if complete but no result", () => {
+ const { container } = render(
+
+ );
+ expect(container).toBeEmptyDOMElement();
+ });
+
+ it("should render completed system metrics card with custom provider", () => {
+ const { getByText } = render(
+
+ );
+ expect(getByText("System Information")).toBeInTheDocument();
+ expect(getByText("Next.js 16 (App Router / Turbopack)")).toBeInTheDocument();
+ expect(getByText("HeroUI v3 + Tailwind CSS v4")).toBeInTheDocument();
+ expect(getByText("operational")).toBeInTheDocument();
+ expect(getByText("development (gemini)")).toBeInTheDocument();
+ });
+ });
+
+ describe("NavigatePageTool", () => {
+ it("should render navigation prompt card and trigger router.push when complete", () => {
+ const { getByText } = render(
+
+ );
+ expect(getByText("/docs")).toBeInTheDocument();
+ expect(getByText("Go now")).toBeInTheDocument();
+ expect(mockPush).toHaveBeenCalledWith("/docs");
+ });
+ });
+ });
+});
diff --git a/tests/unit/components/AIAssistant/thread-messages.test.tsx b/tests/unit/components/AIAssistant/thread-messages.test.tsx
new file mode 100644
index 0000000..8b8f37f
--- /dev/null
+++ b/tests/unit/components/AIAssistant/thread-messages.test.tsx
@@ -0,0 +1,68 @@
+import { describe, it, expect, vi } from "vitest";
+import { render, screen } from "@testing-library/react";
+import { UserMessage, AssistantMessage, AssistantMessageContent } from "@/components/AIAssistant/Thread";
+
+vi.mock("@assistant-ui/react", () => ({
+ MessagePrimitive: {
+ Root: ({ children, className }: { children?: React.ReactNode; className?: string }) => (
+
+ {children}
+
+ ),
+ Content: ({ components }: { components?: Record }) => (
+
+ {components && "components-configured"}
+
+ ),
+ },
+ BranchPickerPrimitive: {
+ Root: ({ children }: { children?: React.ReactNode }) => {children}
,
+ Previous: ({ children, asChild: _asChild }: { children?: React.ReactNode; asChild?: boolean }) => {children}
,
+ Next: ({ children, asChild: _asChild }: { children?: React.ReactNode; asChild?: boolean }) => {children}
,
+ Number: () => 1,
+ Count: () => 2,
+ },
+ ActionBarPrimitive: {
+ Root: ({ children }: { children?: React.ReactNode }) => {children}
,
+ Copy: ({ children, asChild: _asChild }: { children?: React.ReactNode; asChild?: boolean }) => {children}
,
+ Reload: ({ children, asChild: _asChild }: { children?: React.ReactNode; asChild?: boolean }) => {children}
,
+ },
+ ThreadPrimitive: {
+ Root: ({ children }: { children?: React.ReactNode }) => {children}
,
+ Viewport: ({ children }: { children?: React.ReactNode }) => {children}
,
+ Suggestion: ({ children }: { children?: React.ReactNode }) => {children}
,
+ Messages: () => ,
+ },
+ AuiIf: ({ children }: { children?: React.ReactNode }) => <>{children}>,
+}));
+
+vi.mock("@assistant-ui/react-markdown", () => ({
+ MarkdownTextPrimitive: ({ components }: { components?: Record }) => (
+ {components ? "markdown-rendered" : "plain"}
+ ),
+}));
+
+describe("Thread Message Primitives Component Rendering", () => {
+ it("should render UserMessage layout and branch picker controls", () => {
+ render();
+ expect(screen.getByTestId("message-root")).toBeInTheDocument();
+ expect(screen.getByTestId("branch-root")).toBeInTheDocument();
+ expect(screen.getByTestId("branch-num")).toHaveTextContent("1");
+ expect(screen.getByTestId("branch-count")).toHaveTextContent("2");
+ expect(screen.getByRole("button", { name: /previous branch/i })).toBeInTheDocument();
+ expect(screen.getByRole("button", { name: /next branch/i })).toBeInTheDocument();
+ });
+
+ it("should render AssistantMessage layout with action bar controls and tools", () => {
+ render();
+ expect(screen.getByTestId("message-root")).toBeInTheDocument();
+ expect(screen.getByTestId("action-root")).toBeInTheDocument();
+ expect(screen.getByRole("button", { name: /copy assistant message/i })).toBeInTheDocument();
+ expect(screen.getByRole("button", { name: /regenerate response/i })).toBeInTheDocument();
+ });
+
+ it("should render AssistantMessageContent with markdown components mapping", () => {
+ render();
+ expect(screen.getByTestId("markdown-primitive")).toHaveTextContent("markdown-rendered");
+ });
+});
diff --git a/tests/unit/components/ThemeToggle/index.test.tsx b/tests/unit/components/ThemeToggle/index.test.tsx
index 749b459..e5d975b 100644
--- a/tests/unit/components/ThemeToggle/index.test.tsx
+++ b/tests/unit/components/ThemeToggle/index.test.tsx
@@ -14,7 +14,7 @@ describe("ThemeToggle", () => {
vi.clearAllMocks();
});
- it("renders correctly with target theme label", () => {
+ it("renders correctly with target theme label when theme is light", () => {
(useTheme as Mock).mockReturnValue({
resolvedTheme: "light",
setTheme,
@@ -41,7 +41,7 @@ describe("ThemeToggle", () => {
expect(setTheme).toHaveBeenCalledWith("dark");
});
- it("shows correct label when theme is dark", () => {
+ it("shows correct label and icon when theme is dark", () => {
(useTheme as Mock).mockReturnValue({
resolvedTheme: "dark",
setTheme,
@@ -52,5 +52,8 @@ describe("ThemeToggle", () => {
// When dark, it should suggest switching to light
const button = screen.getByLabelText("Switch to light theme");
expect(button).toBeInTheDocument();
+
+ fireEvent.click(button);
+ expect(setTheme).toHaveBeenCalledWith("light");
});
});
diff --git a/tests/unit/docs/app/robots.test.ts b/tests/unit/docs/app/robots.test.ts
new file mode 100644
index 0000000..5a16986
--- /dev/null
+++ b/tests/unit/docs/app/robots.test.ts
@@ -0,0 +1,15 @@
+import { describe, it, expect } from "vitest";
+import robots from "@docs/app/robots";
+
+describe("Docs Robots", () => {
+ it("returns the correct robots configuration", () => {
+ const result = robots();
+ expect(result).toEqual({
+ rules: {
+ userAgent: "*",
+ allow: "/",
+ },
+ sitemap: "https://cur8d.dev/typescript/sitemap.xml",
+ });
+ });
+});
diff --git a/tests/unit/docs/app/sitemap.test.ts b/tests/unit/docs/app/sitemap.test.ts
index 3a62071..f1aaf56 100644
--- a/tests/unit/docs/app/sitemap.test.ts
+++ b/tests/unit/docs/app/sitemap.test.ts
@@ -27,7 +27,7 @@ describe("Docs Sitemap", () => {
vi.useRealTimers();
});
- it("returns correct sitemap based on MDX files", async () => {
+ it("returns correct sitemap based on MDX files including root index and nested index", async () => {
(fs.existsSync as Mock).mockReturnValue(true);
(fs.promises.readdir as Mock).mockImplementation((dir: string) => {
if (dir === CONTENT_DIR) {
@@ -35,6 +35,8 @@ describe("Docs Sitemap", () => {
{ name: "index.mdx", isDirectory: () => false, isFile: () => true },
{ name: "about.md", isDirectory: () => false, isFile: () => true },
{ name: "nested", isDirectory: () => true, isFile: () => false },
+ { name: "guides", isDirectory: () => true, isFile: () => false },
+ { name: "other.txt", isDirectory: () => false, isFile: () => true }, // Non-MDX file ignored
]);
}
if (dir === path.join(CONTENT_DIR, "nested")) {
@@ -42,17 +44,23 @@ describe("Docs Sitemap", () => {
{ name: "page.mdx", isDirectory: () => false, isFile: () => true },
]);
}
+ if (dir === path.join(CONTENT_DIR, "guides")) {
+ return Promise.resolve([
+ { name: "index.md", isDirectory: () => false, isFile: () => true }, // Nested index file
+ ]);
+ }
return Promise.resolve([]);
});
const result = await sitemap();
- expect(result).toHaveLength(3);
+ expect(result).toHaveLength(4);
const urls = result.map((entry: MetadataRoute.Sitemap[number]) => entry.url);
expect(urls).toContain("https://cur8d.dev/typescript/");
expect(urls).toContain("https://cur8d.dev/typescript/about/");
expect(urls).toContain("https://cur8d.dev/typescript/nested/page/");
+ expect(urls).toContain("https://cur8d.dev/typescript/guides/");
const rootEntry = result.find(
(entry: MetadataRoute.Sitemap[number]) => entry.url === "https://cur8d.dev/typescript/",
@@ -71,6 +79,28 @@ describe("Docs Sitemap", () => {
});
});
+ it("handles Windows path separators gracefully", async () => {
+ (fs.existsSync as Mock).mockReturnValue(true);
+ (fs.promises.readdir as Mock).mockImplementation((dir: string) => {
+ if (dir === CONTENT_DIR) {
+ return Promise.resolve([
+ { name: "guide.mdx", isDirectory: () => false, isFile: () => true },
+ ]);
+ }
+ return Promise.resolve([]);
+ });
+
+ const originalSep = path.sep;
+ try {
+ Object.defineProperty(path, "sep", { value: "\\", configurable: true });
+ const result = await sitemap();
+ expect(result).toHaveLength(1);
+ expect(result[0].url).toBe("https://cur8d.dev/typescript/guide/");
+ } finally {
+ Object.defineProperty(path, "sep", { value: originalSep, configurable: true });
+ }
+ });
+
it("returns empty array if content directory does not exist", async () => {
(fs.existsSync as Mock).mockReturnValue(false);
const result = await sitemap();
diff --git a/tests/unit/hooks/use-speech-to-text.test.ts b/tests/unit/hooks/use-speech-to-text.test.ts
new file mode 100644
index 0000000..2f033df
--- /dev/null
+++ b/tests/unit/hooks/use-speech-to-text.test.ts
@@ -0,0 +1,261 @@
+import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
+import { renderHook, act } from "@testing-library/react";
+import { useSpeechToText } from "@/hooks/use-speech-to-text";
+
+describe("useSpeechToText hook", () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ });
+
+ afterEach(() => {
+ delete (window as unknown as Record).SpeechRecognition;
+ delete (window as unknown as Record).webkitSpeechRecognition;
+ });
+
+ it("should initialize with default unsupported state when SpeechRecognition is not available", () => {
+ const { result } = renderHook(() => useSpeechToText());
+ expect(result.current.isSupported).toBe(false);
+ expect(result.current.isListening).toBe(false);
+ expect(result.current.transcript).toBe("");
+ expect(result.current.error).toBeNull();
+
+ // Calling startListening/stopListening when unsupported does not crash
+ act(() => {
+ result.current.startListening();
+ result.current.stopListening();
+ });
+ });
+
+ it("should support webkitSpeechRecognition when standard SpeechRecognition is undefined", () => {
+ const mockStart = vi.fn();
+ const mockStop = vi.fn();
+ const mockAbort = vi.fn();
+
+ class MockWebkitSpeechRecognition {
+ continuous = false;
+ interimResults = false;
+ lang = "en-US";
+ start = mockStart;
+ stop = mockStop;
+ abort = mockAbort;
+ onstart: (() => void) | null = null;
+ onresult: ((event: unknown) => void) | null = null;
+ onerror: ((event: unknown) => void) | null = null;
+ onend: (() => void) | null = null;
+ }
+
+ (window as unknown as Record).webkitSpeechRecognition = MockWebkitSpeechRecognition;
+
+ const { result } = renderHook(() => useSpeechToText({ lang: "fr-FR", continuous: true, interimResults: false }));
+ expect(result.current.isSupported).toBe(true);
+ });
+
+ it("should handle full lifecycle: start, events, toggle, reset, and unmount abort", () => {
+ const mockStart = vi.fn();
+ const mockStop = vi.fn();
+ const mockAbort = vi.fn();
+ let instance: {
+ onstart: (() => void) | null;
+ onresult: ((event: { resultIndex: number; results: Array> }) => void) | null;
+ onerror: ((event: { error: string }) => void) | null;
+ onend: (() => void) | null;
+ } | null = null;
+
+ class MockSpeechRecognition {
+ continuous = false;
+ interimResults = false;
+ lang = "en-US";
+ start = mockStart;
+ stop = mockStop;
+ abort = mockAbort;
+ onstart: (() => void) | null = null;
+ onresult: ((event: { resultIndex: number; results: Array> }) => void) | null = null;
+ onerror: ((event: { error: string }) => void) | null = null;
+ onend: (() => void) | null = null;
+
+ constructor() {
+ instance = this;
+ }
+ }
+
+ (window as unknown as Record).SpeechRecognition = MockSpeechRecognition;
+
+ const onResult = vi.fn();
+ const { result, unmount } = renderHook(() => useSpeechToText({ onResult }));
+
+ expect(result.current.isSupported).toBe(true);
+
+ // Start listening
+ act(() => {
+ result.current.startListening();
+ instance?.onstart?.();
+ });
+ expect(mockStart).toHaveBeenCalled();
+ expect(result.current.isListening).toBe(true);
+
+ // Receive results across multiple segments
+ act(() => {
+ instance?.onresult?.({
+ resultIndex: 0,
+ results: [
+ [{ transcript: "Hello " }],
+ [{ transcript: "world" }],
+ ],
+ });
+ });
+ expect(result.current.transcript).toBe("Hello world");
+ expect(onResult).toHaveBeenCalledWith("Hello world");
+
+ // Toggle listening off
+ act(() => {
+ result.current.toggleListening();
+ });
+ expect(mockStop).toHaveBeenCalled();
+ expect(result.current.isListening).toBe(false);
+
+ // Toggle listening back on
+ act(() => {
+ result.current.toggleListening();
+ });
+ expect(result.current.isListening).toBe(true);
+
+ // Error event
+ act(() => {
+ instance?.onerror?.({ error: "audio-capture" });
+ });
+ expect(result.current.error).toBe("audio-capture");
+ expect(result.current.isListening).toBe(false);
+
+ // End event
+ act(() => {
+ instance?.onend?.();
+ });
+ expect(result.current.isListening).toBe(false);
+
+ // Reset transcript
+ act(() => {
+ result.current.resetTranscript();
+ });
+ expect(result.current.transcript).toBe("");
+ expect(result.current.error).toBeNull();
+
+ // Unmount triggers abort
+ unmount();
+ expect(mockAbort).toHaveBeenCalled();
+ });
+
+ it("should handle empty speech results without onResult callback", () => {
+ let instance: {
+ onresult: ((event: { resultIndex: number; results: Array> }) => void) | null;
+ } | null = null;
+
+ class MockSpeechRecognition {
+ continuous = false;
+ interimResults = false;
+ lang = "en-US";
+ start = vi.fn();
+ stop = vi.fn();
+ abort = vi.fn();
+ onstart = null;
+ onresult = null;
+ onerror = null;
+ onend = null;
+
+ constructor() {
+ instance = this as unknown as { onresult: ((event: { resultIndex: number; results: Array> }) => void) | null };
+ }
+ }
+
+ (window as unknown as Record).SpeechRecognition = MockSpeechRecognition;
+
+ const { result } = renderHook(() => useSpeechToText());
+ act(() => {
+ instance?.onresult?.({
+ resultIndex: 0,
+ results: [[{ transcript: "" }]],
+ });
+ });
+ expect(result.current.transcript).toBe("");
+ });
+
+ it("should catch and record errors when start() throws", () => {
+ class ThrowingStartRecognition {
+ continuous = false;
+ interimResults = false;
+ lang = "en-US";
+ start = () => {
+ throw new Error("Microphone permission denied");
+ };
+ stop = vi.fn();
+ abort = vi.fn();
+ onstart = null;
+ onresult = null;
+ onerror = null;
+ onend = null;
+ }
+
+ (window as unknown as Record).SpeechRecognition = ThrowingStartRecognition;
+
+ const { result } = renderHook(() => useSpeechToText());
+
+ act(() => {
+ result.current.startListening();
+ });
+
+ expect(result.current.error).toBe("Microphone permission denied");
+ });
+
+ it("should handle non-Error exceptions in startListening", () => {
+ class NonErrorThrowingRecognition {
+ continuous = false;
+ interimResults = false;
+ lang = "en-US";
+ start = () => {
+ throw "String error";
+ };
+ stop = vi.fn();
+ abort = vi.fn();
+ onstart = null;
+ onresult = null;
+ onerror = null;
+ onend = null;
+ }
+
+ (window as unknown as Record).SpeechRecognition = NonErrorThrowingRecognition;
+
+ const { result } = renderHook(() => useSpeechToText());
+
+ act(() => {
+ result.current.startListening();
+ });
+
+ expect(result.current.error).toBe("Failed to start speech recognition");
+ });
+
+ it("should safely ignore errors thrown by stop()", () => {
+ class ThrowingStopRecognition {
+ continuous = false;
+ interimResults = false;
+ lang = "en-US";
+ start = vi.fn();
+ stop = () => {
+ throw new Error("Already stopped");
+ };
+ abort = vi.fn();
+ onstart = null;
+ onresult = null;
+ onerror = null;
+ onend = null;
+ }
+
+ (window as unknown as Record).SpeechRecognition = ThrowingStopRecognition;
+
+ const { result } = renderHook(() => useSpeechToText());
+
+ expect(() => {
+ act(() => {
+ result.current.stopListening();
+ });
+ }).not.toThrow();
+ });
+});
diff --git a/tests/unit/lib/ai/config.test.ts b/tests/unit/lib/ai/config.test.ts
new file mode 100644
index 0000000..ba695f3
--- /dev/null
+++ b/tests/unit/lib/ai/config.test.ts
@@ -0,0 +1,165 @@
+import { describe, it, expect, vi, beforeEach } from "vitest";
+import { getModel, defaultModel } from "@/lib/ai/config";
+import { createMockModel, mockModel } from "@/lib/ai/mock-provider";
+import type {
+ LanguageModelV3,
+ LanguageModelV3CallOptions,
+ LanguageModelV3StreamPart,
+} from "@ai-sdk/provider";
+
+describe("AI Config & Model Resolution", () => {
+ beforeEach(() => {
+ vi.unstubAllEnvs();
+ });
+
+ it.each([
+ { provider: "mock", expectedModelId: "cur8d-mock-model" },
+ { provider: "google", expectedModelId: "cur8d-mock-model" },
+ { provider: "openai", expectedModelId: "cur8d-mock-model" },
+ { provider: "anthropic", expectedModelId: "cur8d-mock-model" },
+ { provider: "unknown-provider", expectedModelId: "cur8d-mock-model" },
+ ])(
+ "should return $expectedModelId when provider is $provider without API key",
+ ({ provider, expectedModelId }) => {
+ const model = getModel({
+ provider: provider as "mock" | "google" | "openai" | "anthropic",
+ }) as LanguageModelV3;
+ expect(model.modelId).toBe(expectedModelId);
+ }
+ );
+
+ it("should return google model when API key is provided and default model name if omitted", () => {
+ const model = getModel({ provider: "google", apiKey: "test-google-key" }) as LanguageModelV3;
+ expect(model.modelId).toBe("gemini-2.5-flash");
+ });
+
+ it("should return openai model with default and custom model names", () => {
+ const defaultOpenAI = getModel({ provider: "openai", apiKey: "test-openai-key" }) as LanguageModelV3;
+ expect(defaultOpenAI.modelId).toBe("gpt-4o-mini");
+
+ const customOpenAI = getModel({ provider: "openai", apiKey: "test-openai-key", model: "gpt-4o", baseURL: "https://api.openai.com/v1" }) as LanguageModelV3;
+ expect(customOpenAI.modelId).toBe("gpt-4o");
+ });
+
+ it("should return anthropic model with default and custom model names", () => {
+ const defaultAnthropic = getModel({ provider: "anthropic", apiKey: "test-anthropic-key" }) as LanguageModelV3;
+ expect(defaultAnthropic.modelId).toBe("claude-3-7-sonnet-20250219");
+
+ const customAnthropic = getModel({ provider: "anthropic", apiKey: "test-anthropic-key", model: "claude-3-haiku", baseURL: "https://api.anthropic.com" }) as LanguageModelV3;
+ expect(customAnthropic.modelId).toBe("claude-3-haiku");
+ });
+
+ it("should return custom OpenAI-compatible model with default fallback values", () => {
+ const model = getModel({ provider: "custom" }) as LanguageModelV3;
+ expect(model.modelId).toBe("llama3.2");
+
+ const configuredModel = getModel({ provider: "custom", apiKey: "ollama-key", baseURL: "http://localhost:11434/v1", model: "mistral" }) as LanguageModelV3;
+ expect(configuredModel.modelId).toBe("mistral");
+ });
+
+ it("should resolve defaultModel export properly", () => {
+ expect(defaultModel).toBeDefined();
+ expect((defaultModel as unknown as LanguageModelV3).modelId).toBe("cur8d-mock-model");
+ });
+});
+
+describe("Mock Provider", () => {
+ it("should support doGenerate with various prompt structures", async () => {
+ const model = createMockModel() as unknown as LanguageModelV3;
+
+ // Array of objects with parts
+ const res1 = await model.doGenerate({
+ prompt: [
+ { role: "user", content: [{ type: "text", text: "tell me about features" }] },
+ { role: "user", content: [{ nonText: true }, null] },
+ ],
+ } as unknown as LanguageModelV3CallOptions);
+
+ const firstContent = res1.content[0];
+ if (firstContent.type === "text") {
+ expect(firstContent.text).toContain("Mock AI response");
+ expect(firstContent.text).toContain("tell me about features");
+ }
+ expect(res1.finishReason.unified).toBe("stop");
+
+ // Array of strings and primitive objects
+ const res2 = await model.doGenerate({
+ prompt: ["simple string message", { content: "direct string content" }, { unexpected: true }, 12345],
+ } as unknown as LanguageModelV3CallOptions);
+ const content2 = res2.content[0];
+ if (content2.type === "text") {
+ expect(content2.text).toContain("simple string message");
+ expect(content2.text).toContain("direct string content");
+ }
+
+ // Single string prompt
+ const res3 = await model.doGenerate({
+ prompt: "single string",
+ } as unknown as LanguageModelV3CallOptions);
+ const content3 = res3.content[0];
+ if (content3.type === "text") {
+ expect(content3.text).toContain("single string");
+ }
+
+ // Unknown prompt type
+ const res4 = await model.doGenerate({
+ prompt: null,
+ } as unknown as LanguageModelV3CallOptions);
+ const content4 = res4.content[0];
+ if (content4.type === "text") {
+ expect(content4.text).toContain('Mock AI response for query: "".');
+ }
+ });
+
+ it.each([
+ { prompt: "please switch to dark mode", expectedTool: "setTheme", expectedInput: '{"theme":"dark"}' },
+ { prompt: "please switch to light mode", expectedTool: "setTheme", expectedInput: '{"theme":"light"}' },
+ { prompt: "show system info and stack details", expectedTool: "getSystemInfo", expectedInput: "{}" },
+ { prompt: "search documentation for testing", expectedTool: "searchDocumentation", expectedInput: '{"query":"documentation for testing"}' },
+ { prompt: "show documentation guide", expectedTool: "searchDocumentation", expectedInput: '{"query":"overview"}' },
+ ])(
+ "should simulate $expectedTool tool call when prompt is '$prompt'",
+ async ({ prompt, expectedTool, expectedInput }) => {
+ const model = createMockModel({ simulateTools: true }) as unknown as LanguageModelV3;
+ const res = await model.doStream({
+ prompt,
+ } as unknown as LanguageModelV3CallOptions);
+
+ const reader = res.stream.getReader();
+ const chunk1 = await reader.read();
+ expect(chunk1.value?.type).toBe("tool-call");
+ const toolCall = chunk1.value as Extract;
+ expect(toolCall.toolName).toBe(expectedTool);
+ expect(toolCall.input).toBe(expectedInput);
+
+ const chunk2 = await reader.read();
+ expect(chunk2.value?.type).toBe("finish");
+ }
+ );
+
+ it("should stream default welcome chunks for general prompts", async () => {
+ const model = createMockModel({ simulateTools: false }) as unknown as LanguageModelV3;
+ const res = await model.doStream({
+ prompt: "hello world",
+ } as unknown as LanguageModelV3CallOptions);
+
+ const reader = res.stream.getReader();
+ const chunk1 = await reader.read();
+ expect(chunk1.value?.type).toBe("text-delta");
+ expect((chunk1.value as Extract)?.delta).toContain("Welcome to cur8d AI Assistant");
+
+ // Read until finish
+ let finished = false;
+ while (!finished) {
+ const { value, done } = await reader.read();
+ if (done || value?.type === "finish") {
+ finished = true;
+ }
+ }
+ expect(finished).toBe(true);
+ });
+
+ it("should expose static singleton mockModel", () => {
+ expect(mockModel).toBeDefined();
+ });
+});
diff --git a/tests/unit/lib/ai/tools.test.ts b/tests/unit/lib/ai/tools.test.ts
new file mode 100644
index 0000000..2b6b662
--- /dev/null
+++ b/tests/unit/lib/ai/tools.test.ts
@@ -0,0 +1,87 @@
+import { describe, it, expect } from "vitest";
+import {
+ searchDocumentation,
+ setTheme,
+ getSystemInfo,
+ navigatePage,
+ DOCS_CATALOG,
+ aiTools,
+ type DocItem,
+ type SystemInfo,
+} from "@/lib/ai/tools";
+
+describe("AI Generative Tools", () => {
+ it("should have all required tools registered", () => {
+ expect(aiTools.searchDocumentation).toBeDefined();
+ expect(aiTools.setTheme).toBeDefined();
+ expect(aiTools.getSystemInfo).toBeDefined();
+ expect(aiTools.navigatePage).toBeDefined();
+ });
+
+ describe("searchDocumentation", () => {
+ it("should return matching documentation articles", async () => {
+ const execute = searchDocumentation.execute;
+ if (!execute) throw new Error("searchDocumentation has no execute method");
+
+ const result = (await execute({ query: "testing" }, {} as never)) as { query: string; results: DocItem[] };
+ expect(result.results.length).toBeGreaterThan(0);
+ expect(result.results[0].title).toBe("Testing Guide");
+ });
+
+ it("should return fallback catalog results if no direct match", async () => {
+ const execute = searchDocumentation.execute;
+ if (!execute) throw new Error("searchDocumentation has no execute method");
+
+ const result = (await execute({ query: "xyznonexistent" }, {} as never)) as { query: string; results: DocItem[] };
+ expect(result.results).toHaveLength(3);
+ });
+ });
+
+ describe("setTheme", () => {
+ it("should handle light, dark, and system themes", async () => {
+ const execute = setTheme.execute;
+ if (!execute) throw new Error("setTheme has no execute method");
+
+ const darkResult = (await execute({ theme: "dark" }, {} as never)) as { success: boolean; theme: string };
+ expect(darkResult.success).toBe(true);
+ expect(darkResult.theme).toBe("dark");
+
+ const lightResult = (await execute({ theme: "light" }, {} as never)) as { success: boolean; theme: string };
+ expect(lightResult.success).toBe(true);
+ expect(lightResult.theme).toBe("light");
+ });
+ });
+
+ describe("getSystemInfo", () => {
+ it("should return project stack metadata", async () => {
+ const execute = getSystemInfo.execute;
+ if (!execute) throw new Error("getSystemInfo has no execute method");
+
+ const info = (await execute({}, {} as never)) as SystemInfo;
+ expect(info.name).toBe("cur8d");
+ expect(info.framework).toContain("Next.js");
+ expect(info.designSystem).toContain("HeroUI");
+ expect(info.status).toBe("operational");
+ });
+ });
+
+ describe("navigatePage", () => {
+ it("should return navigation confirmation for given route", async () => {
+ const execute = navigatePage.execute;
+ if (!execute) throw new Error("navigatePage has no execute method");
+
+ const res = (await execute({ route: "/docs" }, {} as never)) as { success: boolean; route: string; message: string };
+ expect(res.success).toBe(true);
+ expect(res.route).toBe("/docs");
+ expect(res.message).toContain("/docs");
+ });
+ });
+
+ describe("DOCS_CATALOG", () => {
+ it("should contain standard doc pages", () => {
+ expect(DOCS_CATALOG.length).toBeGreaterThanOrEqual(5);
+ expect(DOCS_CATALOG.some((d) => d.title === "Project Overview")).toBe(true);
+ expect(DOCS_CATALOG.some((d) => d.title === "AI Assistant")).toBe(true);
+ });
+ });
+});
diff --git a/tests/unit/lib/error-reporting.test.ts b/tests/unit/lib/error-reporting.test.ts
index d1d92bf..dd90dcc 100644
--- a/tests/unit/lib/error-reporting.test.ts
+++ b/tests/unit/lib/error-reporting.test.ts
@@ -10,7 +10,7 @@ describe("error-reporting utility", () => {
vi.restoreAllMocks();
});
- it("logs error to console", () => {
+ it("logs error to console without context", () => {
const error = new Error("Test error");
reportError(error);
expect(console.error).toHaveBeenCalledWith(error);
@@ -51,15 +51,90 @@ describe("error-reporting utility", () => {
it("redacts Vercel Blob tokens in error messages and context strings", () => {
const rawToken = "vercel_blob_rw_1234567890_abcdef";
const error = new Error(`Connection failed using ${rawToken}`);
+ error.stack = `Error: Connection failed using ${rawToken}\n at line 1`;
const context = { details: `Failed to fetch from ${rawToken}` };
reportError(error, context);
const loggedError = (console.error as ReturnType).mock.calls[0][0] as Error;
expect(loggedError.message).toBe("Connection failed using [REDACTED_VERCEL_BLOB_TOKEN]");
+ expect(loggedError.stack).toContain("[REDACTED_VERCEL_BLOB_TOKEN]");
expect(console.error).toHaveBeenCalledWith(
expect.any(Error),
{ details: "Failed to fetch from [REDACTED_VERCEL_BLOB_TOKEN]" }
);
});
+
+ it("handles error without stack properly when sanitizing", () => {
+ const rawToken = "vercel_blob_rw_1234567890_abcdef";
+ const error = new Error(`Connection failed using ${rawToken}`);
+ delete error.stack;
+
+ reportError(error);
+
+ const loggedError = (console.error as ReturnType).mock.calls[0][0] as Error;
+ expect(loggedError.message).toBe("Connection failed using [REDACTED_VERCEL_BLOB_TOKEN]");
+ expect(loggedError.name).toBe("Error");
+ });
+
+ it("handles arrays and sanitizes nested elements", () => {
+ const error = new Error("Array test");
+ const context = {
+ items: ["regular", "vercel_blob_rw_abc123_xyz", { secretKey: "123" }],
+ };
+
+ reportError(error, context);
+
+ expect(console.error).toHaveBeenCalledWith(error, {
+ items: ["regular", "[REDACTED_VERCEL_BLOB_TOKEN]", { secretKey: "[REDACTED]" }],
+ });
+ });
+
+ it("handles circular references gracefully", () => {
+ const error = new Error("Circular test");
+ const circularObj: Record = { name: "loop" };
+ circularObj.self = circularObj;
+
+ reportError(error, { payload: circularObj });
+
+ expect(console.error).toHaveBeenCalledWith(error, {
+ payload: {
+ name: "loop",
+ self: "[CIRCULAR]",
+ },
+ });
+ });
+
+ it("preserves Date, RegExp, and primitive values", () => {
+ const date = new Date("2025-01-01T00:00:00Z");
+ const regex = /test/g;
+ const error = new Error("Types test");
+ const context = {
+ date,
+ regex,
+ count: 42,
+ active: true,
+ empty: null,
+ missing: undefined,
+ };
+
+ reportError(error, context);
+
+ expect(console.error).toHaveBeenCalledWith(error, {
+ date,
+ regex,
+ count: 42,
+ active: true,
+ empty: null,
+ missing: undefined,
+ });
+ });
+
+ it("handles non-Error objects passed as error", () => {
+ reportError("raw string error");
+ expect(console.error).toHaveBeenCalledWith("raw string error");
+
+ reportError(12345);
+ expect(console.error).toHaveBeenCalledWith(12345);
+ });
});