img]:rounded-full [&>img]:bg-background [&>img]:p-px [&>img]:ring-1 dark:[&>img]:bg-foreground",
+ className
+ )}
+ {...props}
+ />
+);
+
+export type ModelSelectorNameProps = ComponentProps<"span">;
+
+export const ModelSelectorName = ({
+ className,
+ ...props
+}: ModelSelectorNameProps) => (
+
+);
diff --git a/web/components/ai-elements/node.spec.tsx b/web/components/ai-elements/node.spec.tsx
new file mode 100644
index 0000000..f1ddacf
--- /dev/null
+++ b/web/components/ai-elements/node.spec.tsx
@@ -0,0 +1,60 @@
+import { render, screen } from "@testing-library/react";
+import type { ComponentProps } from "react";
+
+const mockHandle = jest.fn(
+ ({ type, ...props }: { type: string } & ComponentProps<"div">) => (
+
+ )
+);
+
+jest.mock("@xyflow/react", () => ({
+ __esModule: true,
+ Handle: (props: ComponentProps<"div"> & { type: string }) =>
+ mockHandle(props),
+ Position: {
+ Left: "left",
+ Right: "right",
+ },
+}));
+
+import {
+ Node,
+ NodeContent,
+ NodeFooter,
+ NodeHeader,
+ NodeTitle,
+} from "./node";
+
+describe("Node", () => {
+ beforeEach(() => {
+ mockHandle.mockClear();
+ });
+
+ it("renders handles when enabled and exposes card slots", () => {
+ render(
+
+
+ Title
+
+ Body
+ Footer
+
+ );
+
+ expect(screen.getByTestId("node")).toBeInTheDocument();
+ expect(screen.getByText("Title")).toBeInTheDocument();
+ expect(screen.getByText("Body")).toBeInTheDocument();
+ expect(screen.getByText("Footer")).toBeInTheDocument();
+ expect(screen.getAllByTestId(/handle$/)).toHaveLength(2);
+ });
+
+ it("omits handles that are disabled", () => {
+ render(
+
+ content
+
+ );
+
+ expect(screen.getAllByTestId(/handle$/)).toHaveLength(1);
+ });
+});
diff --git a/web/components/ai-elements/node.tsx b/web/components/ai-elements/node.tsx
new file mode 100644
index 0000000..75ac59a
--- /dev/null
+++ b/web/components/ai-elements/node.tsx
@@ -0,0 +1,71 @@
+import {
+ Card,
+ CardAction,
+ CardContent,
+ CardDescription,
+ CardFooter,
+ CardHeader,
+ CardTitle,
+} from "@/components/ui/card";
+import { cn } from "@/lib/utils";
+import { Handle, Position } from "@xyflow/react";
+import type { ComponentProps } from "react";
+
+export type NodeProps = ComponentProps
& {
+ handles: {
+ target: boolean;
+ source: boolean;
+ };
+};
+
+export const Node = ({ handles, className, ...props }: NodeProps) => (
+
+ {handles.target && }
+ {handles.source && }
+ {props.children}
+
+);
+
+export type NodeHeaderProps = ComponentProps;
+
+export const NodeHeader = ({ className, ...props }: NodeHeaderProps) => (
+
+);
+
+export type NodeTitleProps = ComponentProps;
+
+export const NodeTitle = (props: NodeTitleProps) => ;
+
+export type NodeDescriptionProps = ComponentProps;
+
+export const NodeDescription = (props: NodeDescriptionProps) => (
+
+);
+
+export type NodeActionProps = ComponentProps;
+
+export const NodeAction = (props: NodeActionProps) => ;
+
+export type NodeContentProps = ComponentProps;
+
+export const NodeContent = ({ className, ...props }: NodeContentProps) => (
+
+);
+
+export type NodeFooterProps = ComponentProps;
+
+export const NodeFooter = ({ className, ...props }: NodeFooterProps) => (
+
+);
diff --git a/web/components/ai-elements/open-in-chat.spec.tsx b/web/components/ai-elements/open-in-chat.spec.tsx
new file mode 100644
index 0000000..b22856c
--- /dev/null
+++ b/web/components/ai-elements/open-in-chat.spec.tsx
@@ -0,0 +1,101 @@
+import { render, screen } from "@testing-library/react";
+
+jest.mock("@/components/ui/dropdown-menu", () => ({
+ __esModule: true,
+ DropdownMenu: ({ children }: { children: React.ReactNode }) => (
+ {children}
+ ),
+ DropdownMenuContent: ({
+ children,
+ ...props
+ }: React.ComponentProps<"div">) => (
+
+ {children}
+
+ ),
+ DropdownMenuItem: ({
+ children,
+ asChild: _asChild,
+ ...props
+ }: React.ComponentProps<"div"> & { asChild?: boolean }) => (
+
+ {children}
+
+ ),
+ DropdownMenuLabel: ({
+ children,
+ ...props
+ }: React.ComponentProps<"div">) => {children}
,
+ DropdownMenuSeparator: () =>
,
+ DropdownMenuTrigger: ({
+ children,
+ asChild: _asChild,
+ ...props
+ }: React.ComponentProps<"div"> & { asChild?: boolean }) => (
+
+ {children}
+
+ ),
+}));
+
+import {
+ OpenIn,
+ OpenInChatGPT,
+ OpenInClaude,
+ OpenInContent,
+ OpenInCursor,
+ OpenInLabel,
+ OpenInScira,
+ OpenInSeparator,
+ OpenInT3,
+ OpenInTrigger,
+ OpenInv0,
+} from "./open-in-chat";
+
+describe("OpenIn", () => {
+ it("builds provider links from the shared query", () => {
+ render(
+
+
+
+ Send to
+
+
+
+
+
+
+
+
+
+ );
+
+ expect(screen.getByTestId("dropdown-menu")).toBeInTheDocument();
+ expect(screen.getByTestId("dropdown-separator")).toBeInTheDocument();
+
+ expect(
+ screen.getByRole("link", { name: /Open in ChatGPT/ })
+ ).toHaveAttribute("href", "https://chatgpt.com/?hints=search&prompt=search+term");
+ expect(
+ screen.getByRole("link", { name: /Open in Claude/ })
+ ).toHaveAttribute("href", "https://claude.ai/new?q=search+term");
+ expect(
+ screen.getByRole("link", { name: /Open in T3 Chat/ })
+ ).toHaveAttribute("href", "https://t3.chat/new?q=search+term");
+ expect(
+ screen.getByRole("link", { name: /Open in Scira/ })
+ ).toHaveAttribute("href", "https://scira.ai/?q=search+term");
+ expect(
+ screen.getByRole("link", { name: /Open in v0/ })
+ ).toHaveAttribute("href", "https://v0.app?q=search+term");
+ expect(
+ screen.getByRole("link", { name: /Open in Cursor/ })
+ ).toHaveAttribute("href", "https://cursor.com/link/prompt?text=search+term");
+ });
+
+ it("enforces provider usage within the OpenIn context", () => {
+ expect(() => render( )).toThrow(
+ "OpenIn components must be used within an OpenIn provider"
+ );
+ });
+});
diff --git a/web/components/ai-elements/open-in-chat.tsx b/web/components/ai-elements/open-in-chat.tsx
new file mode 100644
index 0000000..bca3dfe
--- /dev/null
+++ b/web/components/ai-elements/open-in-chat.tsx
@@ -0,0 +1,373 @@
+"use client";
+
+import { Button } from "@/components/ui/button";
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuLabel,
+ DropdownMenuSeparator,
+ DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu";
+import { cn } from "@/lib/utils";
+import {
+ ChevronDownIcon,
+ ExternalLinkIcon,
+ MessageCircleIcon,
+} from "lucide-react";
+import { type ComponentProps, createContext, useContext } from "react";
+
+const providers = {
+ github: {
+ title: "Open in GitHub",
+ createUrl: (url: string) => url,
+ icon: (
+
+ GitHub
+
+
+ ),
+ },
+ scira: {
+ title: "Open in Scira",
+ createUrl: (q: string) =>
+ `https://scira.ai/?${new URLSearchParams({
+ q,
+ })}`,
+ icon: (
+
+ Scira AI
+
+
+
+
+
+
+
+
+ ),
+ },
+ chatgpt: {
+ title: "Open in ChatGPT",
+ createUrl: (prompt: string) =>
+ `https://chatgpt.com/?${new URLSearchParams({
+ hints: "search",
+ prompt,
+ })}`,
+ icon: (
+
+ OpenAI
+
+
+ ),
+ },
+ claude: {
+ title: "Open in Claude",
+ createUrl: (q: string) =>
+ `https://claude.ai/new?${new URLSearchParams({
+ q,
+ })}`,
+ icon: (
+
+ Claude
+
+
+ ),
+ },
+ t3: {
+ title: "Open in T3 Chat",
+ createUrl: (q: string) =>
+ `https://t3.chat/new?${new URLSearchParams({
+ q,
+ })}`,
+ icon: ,
+ },
+ v0: {
+ title: "Open in v0",
+ createUrl: (q: string) =>
+ `https://v0.app?${new URLSearchParams({
+ q,
+ })}`,
+ icon: (
+
+ v0
+
+
+
+ ),
+ },
+ cursor: {
+ title: "Open in Cursor",
+ createUrl: (text: string) => {
+ const url = new URL("https://cursor.com/link/prompt");
+ url.searchParams.set("text", text);
+ return url.toString();
+ },
+ icon: (
+
+ Cursor
+
+
+ ),
+ },
+};
+
+const OpenInContext = createContext<{ query: string } | undefined>(undefined);
+
+const useOpenInContext = () => {
+ const context = useContext(OpenInContext);
+ if (!context) {
+ throw new Error("OpenIn components must be used within an OpenIn provider");
+ }
+ return context;
+};
+
+export type OpenInProps = ComponentProps & {
+ query: string;
+};
+
+export const OpenIn = ({ query, ...props }: OpenInProps) => (
+
+
+
+);
+
+export type OpenInContentProps = ComponentProps;
+
+export const OpenInContent = ({ className, ...props }: OpenInContentProps) => (
+
+);
+
+export type OpenInItemProps = ComponentProps;
+
+export const OpenInItem = (props: OpenInItemProps) => (
+
+);
+
+export type OpenInLabelProps = ComponentProps;
+
+export const OpenInLabel = (props: OpenInLabelProps) => (
+
+);
+
+export type OpenInSeparatorProps = ComponentProps;
+
+export const OpenInSeparator = (props: OpenInSeparatorProps) => (
+
+);
+
+export type OpenInTriggerProps = ComponentProps;
+
+export const OpenInTrigger = ({ children, ...props }: OpenInTriggerProps) => (
+
+ {children ?? (
+
+ Open in chat
+
+
+ )}
+
+);
+
+export type OpenInChatGPTProps = ComponentProps;
+
+export const OpenInChatGPT = (props: OpenInChatGPTProps) => {
+ const { query } = useOpenInContext();
+ return (
+
+
+
+ {providers.chatgpt.icon}
+
+ {providers.chatgpt.title}
+
+
+
+ );
+};
+
+export type OpenInClaudeProps = ComponentProps;
+
+export const OpenInClaude = (props: OpenInClaudeProps) => {
+ const { query } = useOpenInContext();
+ return (
+
+
+
+ {providers.claude.icon}
+
+ {providers.claude.title}
+
+
+
+ );
+};
+
+export type OpenInT3Props = ComponentProps;
+
+export const OpenInT3 = (props: OpenInT3Props) => {
+ const { query } = useOpenInContext();
+ return (
+
+
+ {providers.t3.icon}
+ {providers.t3.title}
+
+
+
+ );
+};
+
+export type OpenInSciraProps = ComponentProps;
+
+export const OpenInScira = (props: OpenInSciraProps) => {
+ const { query } = useOpenInContext();
+ return (
+
+
+
+ {providers.scira.icon}
+
+ {providers.scira.title}
+
+
+
+ );
+};
+
+export type OpenInv0Props = ComponentProps;
+
+export const OpenInv0 = (props: OpenInv0Props) => {
+ const { query } = useOpenInContext();
+ return (
+
+
+ {providers.v0.icon}
+ {providers.v0.title}
+
+
+
+ );
+};
+
+export type OpenInCursorProps = ComponentProps;
+
+export const OpenInCursor = (props: OpenInCursorProps) => {
+ const { query } = useOpenInContext();
+ return (
+
+
+
+ {providers.cursor.icon}
+
+ {providers.cursor.title}
+
+
+
+ );
+};
diff --git a/web/components/ai-elements/panel.spec.tsx b/web/components/ai-elements/panel.spec.tsx
new file mode 100644
index 0000000..d45c662
--- /dev/null
+++ b/web/components/ai-elements/panel.spec.tsx
@@ -0,0 +1,40 @@
+import { render } from "@testing-library/react";
+import type { ComponentProps } from "react";
+import type { PanelProps } from "@xyflow/react";
+
+const mockPanel = jest.fn(
+ ({ children, ...props }: PanelProps & ComponentProps<"div">) => (
+
+ {children}
+
+ )
+);
+
+jest.mock("@xyflow/react", () => ({
+ __esModule: true,
+ Panel: (props: PanelProps & ComponentProps<"div">) => mockPanel(props),
+}));
+
+import { Panel } from "./panel";
+
+describe("Panel", () => {
+ beforeEach(() => mockPanel.mockClear());
+
+ it("applies default styling while forwarding props", () => {
+ render(
+
+ content
+
+ );
+
+ expect(mockPanel).toHaveBeenCalledWith(
+ expect.objectContaining({
+ className: expect.stringContaining("rounded-md"),
+ position: "top-right",
+ })
+ );
+ expect(mockPanel).toHaveBeenCalledWith(
+ expect.objectContaining({ className: expect.stringContaining("custom") })
+ );
+ });
+});
diff --git a/web/components/ai-elements/panel.tsx b/web/components/ai-elements/panel.tsx
new file mode 100644
index 0000000..059cb7a
--- /dev/null
+++ b/web/components/ai-elements/panel.tsx
@@ -0,0 +1,15 @@
+import { cn } from "@/lib/utils";
+import { Panel as PanelPrimitive } from "@xyflow/react";
+import type { ComponentProps } from "react";
+
+type PanelProps = ComponentProps;
+
+export const Panel = ({ className, ...props }: PanelProps) => (
+
+);
diff --git a/web/components/ai-elements/plan.spec.tsx b/web/components/ai-elements/plan.spec.tsx
new file mode 100644
index 0000000..f346f1f
--- /dev/null
+++ b/web/components/ai-elements/plan.spec.tsx
@@ -0,0 +1,149 @@
+import React from "react";
+import { render, screen } from "@testing-library/react";
+
+jest.mock("@/components/ui/collapsible", () => ({
+ __esModule: true,
+ Collapsible: ({
+ children,
+ asChild,
+ ...props
+ }: React.ComponentProps<"div"> & { asChild?: boolean }) =>
+ asChild && React.isValidElement(children) ? (
+ React.cloneElement(children, { ...props, "data-testid": "collapsible" })
+ ) : (
+
+ {children}
+
+ ),
+ CollapsibleContent: ({
+ children,
+ asChild,
+ ...props
+ }: React.ComponentProps<"div"> & { asChild?: boolean }) =>
+ asChild && React.isValidElement(children) ? (
+ React.cloneElement(children, {
+ ...props,
+ "data-testid": "collapsible-content",
+ })
+ ) : (
+
+ {children}
+
+ ),
+ CollapsibleTrigger: ({
+ children,
+ asChild: _asChild,
+ ...props
+ }: React.ComponentProps<"button"> & { asChild?: boolean }) =>
+ _asChild && React.isValidElement(children) ? (
+ React.cloneElement(children, {
+ ...props,
+ "data-testid": "collapsible-trigger",
+ })
+ ) : (
+
+ {children}
+
+ ),
+}));
+
+jest.mock("@/components/ui/card", () => ({
+ __esModule: true,
+ Card: ({ children, ...props }: React.ComponentProps<"div">) => (
+
+ {children}
+
+ ),
+ CardHeader: ({ children, ...props }: React.ComponentProps<"div">) => (
+
+ {children}
+
+ ),
+ CardTitle: ({ children, ...props }: React.ComponentProps<"h3">) => (
+
+ {children}
+
+ ),
+ CardDescription: ({ children, ...props }: React.ComponentProps<"p">) => (
+
+ {children}
+
+ ),
+ CardAction: ({ children, ...props }: React.ComponentProps<"div">) => (
+
+ {children}
+
+ ),
+ CardContent: ({ children, ...props }: React.ComponentProps<"div">) => (
+
+ {children}
+
+ ),
+ CardFooter: ({ children, ...props }: React.ComponentProps<"div">) => (
+
+ {children}
+
+ ),
+}));
+
+jest.mock("./shimmer", () => ({
+ __esModule: true,
+ Shimmer: ({ children }: { children: React.ReactNode }) => (
+ {children}
+ ),
+}));
+
+import {
+ Plan,
+ PlanAction,
+ PlanContent,
+ PlanDescription,
+ PlanFooter,
+ PlanHeader,
+ PlanTitle,
+ PlanTrigger,
+} from "./plan";
+
+describe("Plan", () => {
+ it("renders plan content and trigger", () => {
+ render(
+
+
+ Execution plan
+ Action
+
+
+ Steps to follow
+
+ Content
+
+ Footer
+
+ );
+
+ expect(screen.getByText("Execution plan")).toBeInTheDocument();
+ expect(screen.getByText("Steps to follow")).toBeInTheDocument();
+ expect(screen.getByTestId("collapsible-trigger")).toHaveTextContent(
+ "Toggle plan"
+ );
+ expect(screen.getByText("Content")).toBeInTheDocument();
+ expect(screen.getByText("Footer")).toBeInTheDocument();
+ });
+
+ it("wraps title and description in a shimmer when streaming", () => {
+ render(
+
+
+ Streaming title
+
+ Streaming description
+
+ );
+
+ const shimmers = screen.getAllByTestId("shimmer");
+ expect(shimmers.map(node => node.textContent)).toEqual([
+ "Streaming title",
+ "Streaming description",
+ ]);
+ });
+});
diff --git a/web/components/ai-elements/plan.tsx b/web/components/ai-elements/plan.tsx
new file mode 100644
index 0000000..be04d88
--- /dev/null
+++ b/web/components/ai-elements/plan.tsx
@@ -0,0 +1,142 @@
+"use client";
+
+import { Button } from "@/components/ui/button";
+import {
+ Card,
+ CardAction,
+ CardContent,
+ CardDescription,
+ CardFooter,
+ CardHeader,
+ CardTitle,
+} from "@/components/ui/card";
+import {
+ Collapsible,
+ CollapsibleContent,
+ CollapsibleTrigger,
+} from "@/components/ui/collapsible";
+import { cn } from "@/lib/utils";
+import { ChevronsUpDownIcon } from "lucide-react";
+import type { ComponentProps } from "react";
+import { createContext, useContext } from "react";
+import { Shimmer } from "./shimmer";
+
+type PlanContextValue = {
+ isStreaming: boolean;
+};
+
+const PlanContext = createContext(null);
+
+const usePlan = () => {
+ const context = useContext(PlanContext);
+ if (!context) {
+ throw new Error("Plan components must be used within Plan");
+ }
+ return context;
+};
+
+export type PlanProps = ComponentProps & {
+ isStreaming?: boolean;
+};
+
+export const Plan = ({
+ className,
+ isStreaming = false,
+ children,
+ ...props
+}: PlanProps) => (
+
+
+ {children}
+
+
+);
+
+export type PlanHeaderProps = ComponentProps;
+
+export const PlanHeader = ({ className, ...props }: PlanHeaderProps) => (
+
+);
+
+export type PlanTitleProps = Omit<
+ ComponentProps,
+ "children"
+> & {
+ children: string;
+};
+
+export const PlanTitle = ({ children, ...props }: PlanTitleProps) => {
+ const { isStreaming } = usePlan();
+
+ return (
+
+ {isStreaming ? {children} : children}
+
+ );
+};
+
+export type PlanDescriptionProps = Omit<
+ ComponentProps,
+ "children"
+> & {
+ children: string;
+};
+
+export const PlanDescription = ({
+ className,
+ children,
+ ...props
+}: PlanDescriptionProps) => {
+ const { isStreaming } = usePlan();
+
+ return (
+
+ {isStreaming ? {children} : children}
+
+ );
+};
+
+export type PlanActionProps = ComponentProps;
+
+export const PlanAction = (props: PlanActionProps) => (
+
+);
+
+export type PlanContentProps = ComponentProps;
+
+export const PlanContent = (props: PlanContentProps) => (
+
+
+
+);
+
+export type PlanFooterProps = ComponentProps<"div">;
+
+export const PlanFooter = (props: PlanFooterProps) => (
+
+);
+
+export type PlanTriggerProps = ComponentProps;
+
+export const PlanTrigger = ({ className, ...props }: PlanTriggerProps) => (
+
+
+
+ Toggle plan
+
+
+);
diff --git a/web/components/ai-elements/prompt-input.spec.tsx b/web/components/ai-elements/prompt-input.spec.tsx
new file mode 100644
index 0000000..876cebb
--- /dev/null
+++ b/web/components/ai-elements/prompt-input.spec.tsx
@@ -0,0 +1,102 @@
+import { render, screen, waitFor } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+
+jest.mock("nanoid", () => ({ nanoid: () => "file-id" }));
+
+import {
+ PromptInput,
+ PromptInputAttachments,
+ PromptInputBody,
+ PromptInputFooter,
+ PromptInputHeader,
+ PromptInputSubmit,
+ PromptInputTextarea,
+} from "./prompt-input";
+
+const createObjectURL = jest.fn(() => "blob:mock-url");
+const revokeObjectURL = jest.fn();
+
+beforeAll(() => {
+ global.URL.createObjectURL = createObjectURL;
+ global.URL.revokeObjectURL = revokeObjectURL;
+});
+
+describe("PromptInput", () => {
+ it("collects text and attachments on submit", async () => {
+ const onSubmit = jest.fn();
+ const file = new File(["content"], "hello.txt", { type: "text/plain" });
+
+ global.fetch = jest.fn(async () => new Response(new Blob(["content"])));
+
+ render(
+
+
+
+ {attachment => (
+ {attachment.filename}
+ )}
+
+
+
+
+
+
+
+
+
+ );
+
+ const uploader = screen.getByLabelText("Upload files") as HTMLInputElement;
+ await userEvent.upload(uploader, file);
+ expect(await screen.findByTestId("attachment")).toHaveTextContent(
+ "hello.txt"
+ );
+
+ await userEvent.type(
+ screen.getByPlaceholderText("What would you like to know?"),
+ "Hello world"
+ );
+ await userEvent.click(screen.getByLabelText("Submit"));
+
+ await waitFor(() => expect(onSubmit).toHaveBeenCalled());
+
+ const [payload] = onSubmit.mock.calls[0];
+ expect(payload.text).toBe("Hello world");
+ expect(payload.files).toHaveLength(1);
+ expect(payload.files[0]?.filename).toBe("hello.txt");
+ });
+
+ it("removes the last attachment with backspace when the input is empty", async () => {
+ const file = new File(["content"], "remove.txt", { type: "text/plain" });
+
+ render(
+
+
+
+ {attachment => (
+ {attachment.filename}
+ )}
+
+
+
+
+
+
+
+
+
+ );
+
+ const uploader = screen.getByLabelText("Upload files") as HTMLInputElement;
+ await userEvent.upload(uploader, file);
+ expect(await screen.findByTestId("attachment")).toBeInTheDocument();
+
+ const textarea = screen.getByPlaceholderText("What would you like to know?");
+ await userEvent.click(textarea);
+ await userEvent.keyboard("{Backspace}");
+
+ await waitFor(() => {
+ expect(screen.queryByTestId("attachment")).toBeNull();
+ });
+ });
+});
diff --git a/web/components/ai-elements/prompt-input.tsx b/web/components/ai-elements/prompt-input.tsx
new file mode 100644
index 0000000..6033100
--- /dev/null
+++ b/web/components/ai-elements/prompt-input.tsx
@@ -0,0 +1,1415 @@
+"use client";
+
+import { Button } from "@/components/ui/button";
+import {
+ Command,
+ CommandEmpty,
+ CommandGroup,
+ CommandInput,
+ CommandItem,
+ CommandList,
+ CommandSeparator,
+} from "@/components/ui/command";
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu";
+import {
+ HoverCard,
+ HoverCardContent,
+ HoverCardTrigger,
+} from "@/components/ui/hover-card";
+import {
+ InputGroup,
+ InputGroupAddon,
+ InputGroupButton,
+ InputGroupTextarea,
+} from "@/components/ui/input-group";
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select";
+import { cn } from "@/lib/utils";
+import type { ChatStatus, FileUIPart } from "ai";
+import {
+ CornerDownLeftIcon,
+ ImageIcon,
+ Loader2Icon,
+ MicIcon,
+ PaperclipIcon,
+ PlusIcon,
+ SquareIcon,
+ XIcon,
+} from "lucide-react";
+import { nanoid } from "nanoid";
+import {
+ type ChangeEvent,
+ type ChangeEventHandler,
+ Children,
+ type ClipboardEventHandler,
+ type ComponentProps,
+ createContext,
+ type FormEvent,
+ type FormEventHandler,
+ Fragment,
+ type HTMLAttributes,
+ type KeyboardEventHandler,
+ type PropsWithChildren,
+ type ReactNode,
+ type RefObject,
+ useCallback,
+ useContext,
+ useEffect,
+ useMemo,
+ useRef,
+ useState,
+} from "react";
+
+// ============================================================================
+// Provider Context & Types
+// ============================================================================
+
+export type AttachmentsContext = {
+ files: (FileUIPart & { id: string })[];
+ add: (files: File[] | FileList) => void;
+ remove: (id: string) => void;
+ clear: () => void;
+ openFileDialog: () => void;
+ fileInputRef: RefObject;
+};
+
+export type TextInputContext = {
+ value: string;
+ setInput: (v: string) => void;
+ clear: () => void;
+};
+
+export type PromptInputControllerProps = {
+ textInput: TextInputContext;
+ attachments: AttachmentsContext;
+ /** INTERNAL: Allows PromptInput to register its file textInput + "open" callback */
+ __registerFileInput: (
+ ref: RefObject,
+ open: () => void
+ ) => void;
+};
+
+const PromptInputController = createContext(
+ null
+);
+const ProviderAttachmentsContext = createContext(
+ null
+);
+
+export const usePromptInputController = () => {
+ const ctx = useContext(PromptInputController);
+ if (!ctx) {
+ throw new Error(
+ "Wrap your component inside to use usePromptInputController()."
+ );
+ }
+ return ctx;
+};
+
+// Optional variants (do NOT throw). Useful for dual-mode components.
+const useOptionalPromptInputController = () =>
+ useContext(PromptInputController);
+
+export const useProviderAttachments = () => {
+ const ctx = useContext(ProviderAttachmentsContext);
+ if (!ctx) {
+ throw new Error(
+ "Wrap your component inside to use useProviderAttachments()."
+ );
+ }
+ return ctx;
+};
+
+const useOptionalProviderAttachments = () =>
+ useContext(ProviderAttachmentsContext);
+
+export type PromptInputProviderProps = PropsWithChildren<{
+ initialInput?: string;
+}>;
+
+/**
+ * Optional global provider that lifts PromptInput state outside of PromptInput.
+ * If you don't use it, PromptInput stays fully self-managed.
+ */
+export function PromptInputProvider({
+ initialInput: initialTextInput = "",
+ children,
+}: PromptInputProviderProps) {
+ // ----- textInput state
+ const [textInput, setTextInput] = useState(initialTextInput);
+ const clearInput = useCallback(() => setTextInput(""), []);
+
+ // ----- attachments state (global when wrapped)
+ const [attachmentFiles, setAttachmentFiles] = useState<
+ (FileUIPart & { id: string })[]
+ >([]);
+ const fileInputRef = useRef(null);
+ const openRef = useRef<() => void>(() => {});
+
+ const add = useCallback((files: File[] | FileList) => {
+ const incoming = Array.from(files);
+ if (incoming.length === 0) {
+ return;
+ }
+
+ setAttachmentFiles(prev =>
+ prev.concat(
+ incoming.map(file => ({
+ id: nanoid(),
+ type: "file" as const,
+ url: URL.createObjectURL(file),
+ mediaType: file.type,
+ filename: file.name,
+ }))
+ )
+ );
+ }, []);
+
+ const remove = useCallback((id: string) => {
+ setAttachmentFiles(prev => {
+ const found = prev.find(f => f.id === id);
+ if (found?.url) {
+ URL.revokeObjectURL(found.url);
+ }
+ return prev.filter(f => f.id !== id);
+ });
+ }, []);
+
+ const clear = useCallback(() => {
+ setAttachmentFiles(prev => {
+ for (const f of prev) {
+ if (f.url) {
+ URL.revokeObjectURL(f.url);
+ }
+ }
+ return [];
+ });
+ }, []);
+
+ // Keep a ref to attachments for cleanup on unmount (avoids stale closure)
+ const attachmentsRef = useRef(attachmentFiles);
+
+ useEffect(() => {
+ attachmentsRef.current = attachmentFiles;
+ }, [attachmentFiles]);
+
+ // Cleanup blob URLs on unmount to prevent memory leaks
+ useEffect(() => {
+ return () => {
+ for (const f of attachmentsRef.current) {
+ if (f.url) {
+ URL.revokeObjectURL(f.url);
+ }
+ }
+ };
+ }, []);
+
+ const openFileDialog = useCallback(() => {
+ openRef.current?.();
+ }, []);
+
+ const attachments = useMemo(
+ () => ({
+ files: attachmentFiles,
+ add,
+ remove,
+ clear,
+ openFileDialog,
+ fileInputRef,
+ }),
+ [attachmentFiles, add, remove, clear, openFileDialog]
+ );
+
+ const __registerFileInput = useCallback(
+ (ref: RefObject, open: () => void) => {
+ fileInputRef.current = ref.current;
+ openRef.current = open;
+ },
+ []
+ );
+
+ const controller = useMemo(
+ () => ({
+ textInput: {
+ value: textInput,
+ setInput: setTextInput,
+ clear: clearInput,
+ },
+ attachments,
+ __registerFileInput,
+ }),
+ [textInput, clearInput, attachments, __registerFileInput]
+ );
+
+ return (
+
+
+ {children}
+
+
+ );
+}
+
+// ============================================================================
+// Component Context & Hooks
+// ============================================================================
+
+const LocalAttachmentsContext = createContext(null);
+
+export const usePromptInputAttachments = () => {
+ // Dual-mode: prefer provider if present, otherwise use local
+ const provider = useOptionalProviderAttachments();
+ const local = useContext(LocalAttachmentsContext);
+ const context = provider ?? local;
+ if (!context) {
+ throw new Error(
+ "usePromptInputAttachments must be used within a PromptInput or PromptInputProvider"
+ );
+ }
+ return context;
+};
+
+export type PromptInputAttachmentProps = HTMLAttributes & {
+ data: FileUIPart & { id: string };
+ className?: string;
+};
+
+export function PromptInputAttachment({
+ data,
+ className,
+ ...props
+}: PromptInputAttachmentProps) {
+ const attachments = usePromptInputAttachments();
+
+ const filename = data.filename || "";
+
+ const mediaType =
+ data.mediaType?.startsWith("image/") && data.url ? "image" : "file";
+ const isImage = mediaType === "image";
+
+ const attachmentLabel = filename || (isImage ? "Image" : "Attachment");
+
+ return (
+
+
+
+
+
+ {isImage ? (
+
+ ) : (
+
+ )}
+
+
{
+ e.stopPropagation();
+ attachments.remove(data.id);
+ }}
+ type="button"
+ variant="ghost"
+ >
+
+ Remove
+
+
+
+
{attachmentLabel}
+
+
+
+
+ {isImage && (
+
+
+
+ )}
+
+
+
+ {filename || (isImage ? "Image" : "Attachment")}
+
+ {data.mediaType && (
+
+ {data.mediaType}
+
+ )}
+
+
+
+
+
+ );
+}
+
+export type PromptInputAttachmentsProps = Omit<
+ HTMLAttributes,
+ "children"
+> & {
+ children: (attachment: FileUIPart & { id: string }) => ReactNode;
+};
+
+export function PromptInputAttachments({
+ children,
+ className,
+ ...props
+}: PromptInputAttachmentsProps) {
+ const attachments = usePromptInputAttachments();
+
+ if (!attachments.files.length) {
+ return null;
+ }
+
+ return (
+
+ {attachments.files.map(file => (
+ {children(file)}
+ ))}
+
+ );
+}
+
+export type PromptInputActionAddAttachmentsProps = ComponentProps<
+ typeof DropdownMenuItem
+> & {
+ label?: string;
+};
+
+export const PromptInputActionAddAttachments = ({
+ label = "Add photos or files",
+ ...props
+}: PromptInputActionAddAttachmentsProps) => {
+ const attachments = usePromptInputAttachments();
+
+ return (
+ {
+ e.preventDefault();
+ attachments.openFileDialog();
+ }}
+ >
+ {label}
+
+ );
+};
+
+export type PromptInputMessage = {
+ text: string;
+ files: FileUIPart[];
+};
+
+export type PromptInputProps = Omit<
+ HTMLAttributes,
+ "onSubmit" | "onError"
+> & {
+ accept?: string; // e.g., "image/*" or leave undefined for any
+ multiple?: boolean;
+ // When true, accepts drops anywhere on document. Default false (opt-in).
+ globalDrop?: boolean;
+ // Render a hidden input with given name and keep it in sync for native form posts. Default false.
+ syncHiddenInput?: boolean;
+ // Minimal constraints
+ maxFiles?: number;
+ maxFileSize?: number; // bytes
+ onError?: (err: {
+ code: "max_files" | "max_file_size" | "accept";
+ message: string;
+ }) => void;
+ onSubmit: (
+ message: PromptInputMessage,
+ event: FormEvent
+ ) => void | Promise;
+};
+
+export const PromptInput = ({
+ className,
+ accept,
+ multiple,
+ globalDrop,
+ syncHiddenInput,
+ maxFiles,
+ maxFileSize,
+ onError,
+ onSubmit,
+ children,
+ ...props
+}: PromptInputProps) => {
+ // Try to use a provider controller if present
+ const controller = useOptionalPromptInputController();
+ const usingProvider = !!controller;
+
+ // Refs
+ const inputRef = useRef(null);
+ const formRef = useRef(null);
+
+ // ----- Local attachments (only used when no provider)
+ const [items, setItems] = useState<(FileUIPart & { id: string })[]>([]);
+ const files = usingProvider ? controller.attachments.files : items;
+
+ // Keep a ref to files for cleanup on unmount (avoids stale closure)
+ const filesRef = useRef(files);
+
+ useEffect(() => {
+ filesRef.current = files;
+ }, [files]);
+
+ const openFileDialogLocal = useCallback(() => {
+ inputRef.current?.click();
+ }, []);
+
+ const matchesAccept = useCallback(
+ (f: File) => {
+ if (!accept || accept.trim() === "") {
+ return true;
+ }
+ if (accept.includes("image/*")) {
+ return f.type.startsWith("image/");
+ }
+ // NOTE: keep simple; expand as needed
+ return true;
+ },
+ [accept]
+ );
+
+ const addLocal = useCallback(
+ (fileList: File[] | FileList) => {
+ const incoming = Array.from(fileList);
+ const accepted = incoming.filter(f => matchesAccept(f));
+ if (incoming.length && accepted.length === 0) {
+ onError?.({
+ code: "accept",
+ message: "No files match the accepted types.",
+ });
+ return;
+ }
+ const withinSize = (f: File) =>
+ maxFileSize ? f.size <= maxFileSize : true;
+ const sized = accepted.filter(withinSize);
+ if (accepted.length > 0 && sized.length === 0) {
+ onError?.({
+ code: "max_file_size",
+ message: "All files exceed the maximum size.",
+ });
+ return;
+ }
+
+ setItems(prev => {
+ const capacity =
+ typeof maxFiles === "number"
+ ? Math.max(0, maxFiles - prev.length)
+ : undefined;
+ const capped =
+ typeof capacity === "number" ? sized.slice(0, capacity) : sized;
+ if (typeof capacity === "number" && sized.length > capacity) {
+ onError?.({
+ code: "max_files",
+ message: "Too many files. Some were not added.",
+ });
+ }
+ const next: (FileUIPart & { id: string })[] = [];
+ for (const file of capped) {
+ next.push({
+ id: nanoid(),
+ type: "file",
+ url: URL.createObjectURL(file),
+ mediaType: file.type,
+ filename: file.name,
+ });
+ }
+ return prev.concat(next);
+ });
+ },
+ [matchesAccept, maxFiles, maxFileSize, onError]
+ );
+
+ const removeLocal = useCallback(
+ (id: string) =>
+ setItems(prev => {
+ const found = prev.find(file => file.id === id);
+ if (found?.url) {
+ URL.revokeObjectURL(found.url);
+ }
+ return prev.filter(file => file.id !== id);
+ }),
+ []
+ );
+
+ const clearLocal = useCallback(
+ () =>
+ setItems(prev => {
+ for (const file of prev) {
+ if (file.url) {
+ URL.revokeObjectURL(file.url);
+ }
+ }
+ return [];
+ }),
+ []
+ );
+
+ const add = usingProvider ? controller.attachments.add : addLocal;
+ const remove = usingProvider ? controller.attachments.remove : removeLocal;
+ const clear = usingProvider ? controller.attachments.clear : clearLocal;
+ const openFileDialog = usingProvider
+ ? controller.attachments.openFileDialog
+ : openFileDialogLocal;
+
+ // Let provider know about our hidden file input so external menus can call openFileDialog()
+ useEffect(() => {
+ if (!usingProvider) return;
+ controller.__registerFileInput(inputRef, () => inputRef.current?.click());
+ }, [usingProvider, controller]);
+
+ // Note: File input cannot be programmatically set for security reasons
+ // The syncHiddenInput prop is no longer functional
+ useEffect(() => {
+ if (syncHiddenInput && inputRef.current && files.length === 0) {
+ inputRef.current.value = "";
+ }
+ }, [files, syncHiddenInput]);
+
+ // Attach drop handlers on nearest form and document (opt-in)
+ useEffect(() => {
+ const form = formRef.current;
+ if (!form) return;
+
+ const onDragOver = (e: DragEvent) => {
+ if (e.dataTransfer?.types?.includes("Files")) {
+ e.preventDefault();
+ }
+ };
+ const onDrop = (e: DragEvent) => {
+ if (e.dataTransfer?.types?.includes("Files")) {
+ e.preventDefault();
+ }
+ if (e.dataTransfer?.files && e.dataTransfer.files.length > 0) {
+ add(e.dataTransfer.files);
+ }
+ };
+ form.addEventListener("dragover", onDragOver);
+ form.addEventListener("drop", onDrop);
+ return () => {
+ form.removeEventListener("dragover", onDragOver);
+ form.removeEventListener("drop", onDrop);
+ };
+ }, [add]);
+
+ useEffect(() => {
+ if (!globalDrop) return;
+
+ const onDragOver = (e: DragEvent) => {
+ if (e.dataTransfer?.types?.includes("Files")) {
+ e.preventDefault();
+ }
+ };
+ const onDrop = (e: DragEvent) => {
+ if (e.dataTransfer?.types?.includes("Files")) {
+ e.preventDefault();
+ }
+ if (e.dataTransfer?.files && e.dataTransfer.files.length > 0) {
+ add(e.dataTransfer.files);
+ }
+ };
+ document.addEventListener("dragover", onDragOver);
+ document.addEventListener("drop", onDrop);
+ return () => {
+ document.removeEventListener("dragover", onDragOver);
+ document.removeEventListener("drop", onDrop);
+ };
+ }, [add, globalDrop]);
+
+ useEffect(
+ () => () => {
+ if (!usingProvider) {
+ for (const f of filesRef.current) {
+ if (f.url) URL.revokeObjectURL(f.url);
+ }
+ }
+ },
+
+ [usingProvider]
+ );
+
+ const handleChange: ChangeEventHandler = event => {
+ if (event.currentTarget.files) {
+ add(event.currentTarget.files);
+ }
+ // Reset input value to allow selecting files that were previously removed
+ event.currentTarget.value = "";
+ };
+
+ const convertBlobUrlToDataUrl = async (
+ url: string
+ ): Promise => {
+ try {
+ const response = await fetch(url);
+ const blob = await response.blob();
+ return new Promise(resolve => {
+ const reader = new FileReader();
+ reader.onloadend = () => resolve(reader.result as string);
+ reader.onerror = () => resolve(null);
+ reader.readAsDataURL(blob);
+ });
+ } catch {
+ return null;
+ }
+ };
+
+ const ctx = useMemo(
+ () => ({
+ files: files.map(item => ({ ...item, id: item.id })),
+ add,
+ remove,
+ clear,
+ openFileDialog,
+ fileInputRef: inputRef,
+ }),
+ [files, add, remove, clear, openFileDialog]
+ );
+
+ const handleSubmit: FormEventHandler = event => {
+ event.preventDefault();
+
+ const form = event.currentTarget;
+ const text = usingProvider
+ ? controller.textInput.value
+ : (() => {
+ const formData = new FormData(form);
+ return (formData.get("message") as string) || "";
+ })();
+
+ // Reset form immediately after capturing text to avoid race condition
+ // where user input during async blob conversion would be lost
+ if (!usingProvider) {
+ form.reset();
+ }
+
+ // Convert blob URLs to data URLs asynchronously
+ Promise.all(
+ files.map(async ({ id: _id, ...item }) => {
+ if (item.url && item.url.startsWith("blob:")) {
+ const dataUrl = await convertBlobUrlToDataUrl(item.url);
+ // If conversion failed, keep the original blob URL
+ return {
+ ...item,
+ url: dataUrl ?? item.url,
+ };
+ }
+ return item;
+ })
+ )
+ .then((convertedFiles: FileUIPart[]) => {
+ try {
+ const result = onSubmit({ text, files: convertedFiles }, event);
+
+ // Handle both sync and async onSubmit
+ if (result instanceof Promise) {
+ result
+ .then(() => {
+ clear();
+ if (usingProvider) {
+ controller.textInput.clear();
+ }
+ })
+ .catch(() => {
+ // Don't clear on error - user may want to retry
+ });
+ } else {
+ // Sync function completed without throwing, clear attachments
+ clear();
+ if (usingProvider) {
+ controller.textInput.clear();
+ }
+ }
+ } catch {
+ // Don't clear on error - user may want to retry
+ }
+ })
+ .catch(() => {
+ // Don't clear on error - user may want to retry
+ });
+ };
+
+ // Render with or without local provider
+ const inner = (
+ <>
+
+
+ >
+ );
+
+ return usingProvider ? (
+ inner
+ ) : (
+
+ {inner}
+
+ );
+};
+
+export type PromptInputBodyProps = HTMLAttributes;
+
+export const PromptInputBody = ({
+ className,
+ ...props
+}: PromptInputBodyProps) => (
+
+);
+
+export type PromptInputTextareaProps = ComponentProps<
+ typeof InputGroupTextarea
+>;
+
+export const PromptInputTextarea = ({
+ onChange,
+ className,
+ placeholder = "What would you like to know?",
+ ...props
+}: PromptInputTextareaProps) => {
+ const controller = useOptionalPromptInputController();
+ const attachments = usePromptInputAttachments();
+ const [isComposing, setIsComposing] = useState(false);
+
+ const handleKeyDown: KeyboardEventHandler = e => {
+ if (e.key === "Enter") {
+ if (isComposing || e.nativeEvent.isComposing) {
+ return;
+ }
+ if (e.shiftKey) {
+ return;
+ }
+ e.preventDefault();
+
+ // Check if the submit button is disabled before submitting
+ const form = e.currentTarget.form;
+ const submitButton = form?.querySelector(
+ 'button[type="submit"]'
+ ) as HTMLButtonElement | null;
+ if (submitButton?.disabled) {
+ return;
+ }
+
+ form?.requestSubmit();
+ }
+
+ // Remove last attachment when Backspace is pressed and textarea is empty
+ if (
+ e.key === "Backspace" &&
+ e.currentTarget.value === "" &&
+ attachments.files.length > 0
+ ) {
+ e.preventDefault();
+ const lastAttachment = attachments.files.at(-1);
+ if (lastAttachment) {
+ attachments.remove(lastAttachment.id);
+ }
+ }
+ };
+
+ const handlePaste: ClipboardEventHandler = event => {
+ const items = event.clipboardData?.items;
+
+ if (!items) {
+ return;
+ }
+
+ const files: File[] = [];
+
+ for (const item of items) {
+ if (item.kind === "file") {
+ const file = item.getAsFile();
+ if (file) {
+ files.push(file);
+ }
+ }
+ }
+
+ if (files.length > 0) {
+ event.preventDefault();
+ attachments.add(files);
+ }
+ };
+
+ const controlledProps = controller
+ ? {
+ value: controller.textInput.value,
+ onChange: (e: ChangeEvent) => {
+ controller.textInput.setInput(e.currentTarget.value);
+ onChange?.(e);
+ },
+ }
+ : {
+ onChange,
+ };
+
+ return (
+ setIsComposing(false)}
+ onCompositionStart={() => setIsComposing(true)}
+ onKeyDown={handleKeyDown}
+ onPaste={handlePaste}
+ placeholder={placeholder}
+ {...props}
+ {...controlledProps}
+ />
+ );
+};
+
+export type PromptInputHeaderProps = Omit<
+ ComponentProps,
+ "align"
+>;
+
+export const PromptInputHeader = ({
+ className,
+ ...props
+}: PromptInputHeaderProps) => (
+
+);
+
+export type PromptInputFooterProps = Omit<
+ ComponentProps,
+ "align"
+>;
+
+export const PromptInputFooter = ({
+ className,
+ ...props
+}: PromptInputFooterProps) => (
+
+);
+
+export type PromptInputToolsProps = HTMLAttributes;
+
+export const PromptInputTools = ({
+ className,
+ ...props
+}: PromptInputToolsProps) => (
+
+);
+
+export type PromptInputButtonProps = ComponentProps;
+
+export const PromptInputButton = ({
+ variant = "ghost",
+ className,
+ size,
+ ...props
+}: PromptInputButtonProps) => {
+ const newSize =
+ size ?? (Children.count(props.children) > 1 ? "sm" : "icon-sm");
+
+ return (
+
+ );
+};
+
+export type PromptInputActionMenuProps = ComponentProps;
+export const PromptInputActionMenu = (props: PromptInputActionMenuProps) => (
+
+);
+
+export type PromptInputActionMenuTriggerProps = PromptInputButtonProps;
+
+export const PromptInputActionMenuTrigger = ({
+ className,
+ children,
+ ...props
+}: PromptInputActionMenuTriggerProps) => (
+
+
+ {children ?? }
+
+
+);
+
+export type PromptInputActionMenuContentProps = ComponentProps<
+ typeof DropdownMenuContent
+>;
+export const PromptInputActionMenuContent = ({
+ className,
+ ...props
+}: PromptInputActionMenuContentProps) => (
+
+);
+
+export type PromptInputActionMenuItemProps = ComponentProps<
+ typeof DropdownMenuItem
+>;
+export const PromptInputActionMenuItem = ({
+ className,
+ ...props
+}: PromptInputActionMenuItemProps) => (
+
+);
+
+// Note: Actions that perform side-effects (like opening a file dialog)
+// are provided in opt-in modules (e.g., prompt-input-attachments).
+
+export type PromptInputSubmitProps = ComponentProps & {
+ status?: ChatStatus;
+};
+
+export const PromptInputSubmit = ({
+ className,
+ variant = "default",
+ size = "icon-sm",
+ status,
+ children,
+ ...props
+}: PromptInputSubmitProps) => {
+ let Icon = ;
+
+ if (status === "submitted") {
+ Icon = ;
+ } else if (status === "streaming") {
+ Icon = ;
+ } else if (status === "error") {
+ Icon = ;
+ }
+
+ return (
+
+ {children ?? Icon}
+
+ );
+};
+
+interface SpeechRecognition extends EventTarget {
+ continuous: boolean;
+ interimResults: boolean;
+ lang: string;
+ start(): void;
+ stop(): void;
+ onstart: ((this: SpeechRecognition, ev: Event) => void) | null;
+ onend: ((this: SpeechRecognition, ev: Event) => void) | null;
+ onresult:
+ | ((this: SpeechRecognition, ev: SpeechRecognitionEvent) => void)
+ | null;
+ onerror:
+ | ((this: SpeechRecognition, ev: SpeechRecognitionErrorEvent) => void)
+ | null;
+}
+
+interface SpeechRecognitionEvent extends Event {
+ results: SpeechRecognitionResultList;
+ resultIndex: number;
+}
+
+type SpeechRecognitionResultList = {
+ readonly length: number;
+ item(index: number): SpeechRecognitionResult;
+ [index: number]: SpeechRecognitionResult;
+};
+
+type SpeechRecognitionResult = {
+ readonly length: number;
+ item(index: number): SpeechRecognitionAlternative;
+ [index: number]: SpeechRecognitionAlternative;
+ isFinal: boolean;
+};
+
+type SpeechRecognitionAlternative = {
+ transcript: string;
+ confidence: number;
+};
+
+interface SpeechRecognitionErrorEvent extends Event {
+ error: string;
+}
+
+declare global {
+ interface Window {
+ SpeechRecognition: {
+ new (): SpeechRecognition;
+ };
+ webkitSpeechRecognition: {
+ new (): SpeechRecognition;
+ };
+ }
+}
+
+export type PromptInputSpeechButtonProps = ComponentProps<
+ typeof PromptInputButton
+> & {
+ textareaRef?: RefObject;
+ onTranscriptionChange?: (text: string) => void;
+};
+
+export const PromptInputSpeechButton = ({
+ className,
+ textareaRef,
+ onTranscriptionChange,
+ ...props
+}: PromptInputSpeechButtonProps) => {
+ const [isListening, setIsListening] = useState(false);
+ const recognitionRef = useRef(null);
+ const canListen = useMemo(
+ () =>
+ typeof window !== "undefined" &&
+ ("SpeechRecognition" in window || "webkitSpeechRecognition" in window),
+ []
+ );
+
+ const ensureRecognition = useCallback(() => {
+ if (!canListen) {
+ return null;
+ }
+
+ if (recognitionRef.current) {
+ return recognitionRef.current;
+ }
+
+ const SpeechRecognition =
+ window.SpeechRecognition || window.webkitSpeechRecognition;
+ const instance = new SpeechRecognition();
+
+ instance.continuous = true;
+ instance.interimResults = true;
+ instance.lang = "en-US";
+
+ instance.onstart = () => {
+ setIsListening(true);
+ };
+
+ instance.onend = () => {
+ setIsListening(false);
+ };
+
+ instance.onresult = (event: SpeechRecognitionEvent) => {
+ let finalTranscript = "";
+
+ for (let i = event.resultIndex; i < event.results.length; i++) {
+ const result = event.results[i];
+ if (result.isFinal) {
+ finalTranscript += result[0]?.transcript ?? "";
+ }
+ }
+
+ if (finalTranscript) {
+ const currentValue = textareaRef?.current?.value ?? "";
+ const newValue =
+ currentValue + (currentValue ? " " : "") + finalTranscript;
+
+ onTranscriptionChange?.(newValue);
+ }
+ };
+
+ instance.onerror = (event: SpeechRecognitionErrorEvent) => {
+ console.error("Speech recognition error:", event.error);
+ setIsListening(false);
+ };
+
+ recognitionRef.current = instance;
+ return instance;
+ }, [canListen, onTranscriptionChange, textareaRef]);
+
+ useEffect(() => {
+ return () => {
+ recognitionRef.current?.stop();
+ recognitionRef.current = null;
+ };
+ }, []);
+
+ const toggleListening = useCallback(() => {
+ const recognition = ensureRecognition();
+ if (!recognition) {
+ return;
+ }
+
+ if (isListening) {
+ recognition.stop();
+ } else {
+ recognition.start();
+ }
+ }, [ensureRecognition, isListening]);
+
+ return (
+
+
+
+ );
+};
+
+export type PromptInputSelectProps = ComponentProps;
+
+export const PromptInputSelect = (props: PromptInputSelectProps) => (
+
+);
+
+export type PromptInputSelectTriggerProps = ComponentProps<
+ typeof SelectTrigger
+>;
+
+export const PromptInputSelectTrigger = ({
+ className,
+ ...props
+}: PromptInputSelectTriggerProps) => (
+
+);
+
+export type PromptInputSelectContentProps = ComponentProps<
+ typeof SelectContent
+>;
+
+export const PromptInputSelectContent = ({
+ className,
+ ...props
+}: PromptInputSelectContentProps) => (
+
+);
+
+export type PromptInputSelectItemProps = ComponentProps;
+
+export const PromptInputSelectItem = ({
+ className,
+ ...props
+}: PromptInputSelectItemProps) => (
+
+);
+
+export type PromptInputSelectValueProps = ComponentProps;
+
+export const PromptInputSelectValue = ({
+ className,
+ ...props
+}: PromptInputSelectValueProps) => (
+
+);
+
+export type PromptInputHoverCardProps = ComponentProps;
+
+export const PromptInputHoverCard = ({
+ openDelay = 0,
+ closeDelay = 0,
+ ...props
+}: PromptInputHoverCardProps) => (
+
+);
+
+export type PromptInputHoverCardTriggerProps = ComponentProps<
+ typeof HoverCardTrigger
+>;
+
+export const PromptInputHoverCardTrigger = (
+ props: PromptInputHoverCardTriggerProps
+) => ;
+
+export type PromptInputHoverCardContentProps = ComponentProps<
+ typeof HoverCardContent
+>;
+
+export const PromptInputHoverCardContent = ({
+ align = "start",
+ ...props
+}: PromptInputHoverCardContentProps) => (
+
+);
+
+export type PromptInputTabsListProps = HTMLAttributes;
+
+export const PromptInputTabsList = ({
+ className,
+ ...props
+}: PromptInputTabsListProps) =>
;
+
+export type PromptInputTabProps = HTMLAttributes;
+
+export const PromptInputTab = ({
+ className,
+ ...props
+}: PromptInputTabProps) =>
;
+
+export type PromptInputTabLabelProps = HTMLAttributes;
+
+export const PromptInputTabLabel = ({
+ className,
+ ...props
+}: PromptInputTabLabelProps) => (
+
+);
+
+export type PromptInputTabBodyProps = HTMLAttributes;
+
+export const PromptInputTabBody = ({
+ className,
+ ...props
+}: PromptInputTabBodyProps) => (
+
+);
+
+export type PromptInputTabItemProps = HTMLAttributes;
+
+export const PromptInputTabItem = ({
+ className,
+ ...props
+}: PromptInputTabItemProps) => (
+
+);
+
+export type PromptInputCommandProps = ComponentProps;
+
+export const PromptInputCommand = ({
+ className,
+ ...props
+}: PromptInputCommandProps) => ;
+
+export type PromptInputCommandInputProps = ComponentProps;
+
+export const PromptInputCommandInput = ({
+ className,
+ ...props
+}: PromptInputCommandInputProps) => (
+
+);
+
+export type PromptInputCommandListProps = ComponentProps;
+
+export const PromptInputCommandList = ({
+ className,
+ ...props
+}: PromptInputCommandListProps) => (
+
+);
+
+export type PromptInputCommandEmptyProps = ComponentProps;
+
+export const PromptInputCommandEmpty = ({
+ className,
+ ...props
+}: PromptInputCommandEmptyProps) => (
+
+);
+
+export type PromptInputCommandGroupProps = ComponentProps;
+
+export const PromptInputCommandGroup = ({
+ className,
+ ...props
+}: PromptInputCommandGroupProps) => (
+
+);
+
+export type PromptInputCommandItemProps = ComponentProps;
+
+export const PromptInputCommandItem = ({
+ className,
+ ...props
+}: PromptInputCommandItemProps) => (
+
+);
+
+export type PromptInputCommandSeparatorProps = ComponentProps<
+ typeof CommandSeparator
+>;
+
+export const PromptInputCommandSeparator = ({
+ className,
+ ...props
+}: PromptInputCommandSeparatorProps) => (
+
+);
diff --git a/web/components/ai-elements/queue.spec.tsx b/web/components/ai-elements/queue.spec.tsx
new file mode 100644
index 0000000..ab40588
--- /dev/null
+++ b/web/components/ai-elements/queue.spec.tsx
@@ -0,0 +1,127 @@
+import React from "react";
+import { render, screen } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+
+jest.mock("@/components/ui/collapsible", () => ({
+ __esModule: true,
+ Collapsible: ({
+ children,
+ asChild,
+ defaultOpen: _defaultOpen,
+ ...props
+ }: React.ComponentProps<"div"> & { defaultOpen?: boolean }) =>
+ asChild && React.isValidElement(children) ? (
+ React.cloneElement(children, props)
+ ) : (
+
+ {children}
+
+ ),
+ CollapsibleContent: ({
+ children,
+ ...props
+ }: React.ComponentProps<"div"> & { asChild?: boolean }) =>
+ React.isValidElement(children) ? (
+ React.cloneElement(children, props)
+ ) : (
+
+ {children}
+
+ ),
+ CollapsibleTrigger: ({
+ children,
+ asChild: _asChild,
+ ...props
+ }: React.ComponentProps<"button"> & { asChild?: boolean }) =>
+ _asChild && React.isValidElement(children) ? (
+ React.cloneElement(children, props)
+ ) : (
+
+ {children}
+
+ ),
+}));
+
+jest.mock("@/components/ui/scroll-area", () => ({
+ __esModule: true,
+ ScrollArea: ({
+ children,
+ ...props
+ }: React.ComponentProps<"div">) => (
+
+ {children}
+
+ ),
+ ScrollBar: ({
+ children,
+ ...props
+ }: React.ComponentProps<"div">) => (
+
+ {children}
+
+ ),
+}));
+
+import {
+ Queue,
+ QueueItem,
+ QueueItemAction,
+ QueueItemContent,
+ QueueItemDescription,
+ QueueItemIndicator,
+ QueueList,
+ QueueSection,
+ QueueSectionContent,
+ QueueSectionLabel,
+ QueueSectionTrigger,
+} from "./queue";
+
+describe("Queue", () => {
+ it("renders queue items with indicators and descriptions", () => {
+ render(
+
+
+
+ !}
+ label="tasks"
+ />
+
+
+
+
+
+ First
+ Details
+
+
+
+
+
+ );
+
+ expect(screen.getByText("2 tasks")).toBeInTheDocument();
+ expect(screen.getByText("First")).toBeInTheDocument();
+ expect(screen.getByText("Details")).toBeInTheDocument();
+ expect(screen.getByTestId("scroll-area")).toBeInTheDocument();
+ });
+
+ it("marks completed items and triggers actions", async () => {
+ const onClick = jest.fn();
+ render(
+
+
+ Done
+ Wrapped up
+ Remove
+
+ );
+
+ expect(screen.getByText("Done")).toHaveClass("line-through");
+ expect(screen.getByText("Wrapped up")).toHaveClass("line-through");
+
+ await userEvent.click(screen.getByRole("button", { name: "Remove" }));
+ expect(onClick).toHaveBeenCalled();
+ });
+});
diff --git a/web/components/ai-elements/queue.tsx b/web/components/ai-elements/queue.tsx
new file mode 100644
index 0000000..0c91d13
--- /dev/null
+++ b/web/components/ai-elements/queue.tsx
@@ -0,0 +1,274 @@
+"use client";
+
+import { Button } from "@/components/ui/button";
+import {
+ Collapsible,
+ CollapsibleContent,
+ CollapsibleTrigger,
+} from "@/components/ui/collapsible";
+import { ScrollArea } from "@/components/ui/scroll-area";
+import { cn } from "@/lib/utils";
+import { ChevronDownIcon, PaperclipIcon } from "lucide-react";
+import type { ComponentProps } from "react";
+
+export type QueueMessagePart = {
+ type: string;
+ text?: string;
+ url?: string;
+ filename?: string;
+ mediaType?: string;
+};
+
+export type QueueMessage = {
+ id: string;
+ parts: QueueMessagePart[];
+};
+
+export type QueueTodo = {
+ id: string;
+ title: string;
+ description?: string;
+ status?: "pending" | "completed";
+};
+
+export type QueueItemProps = ComponentProps<"li">;
+
+export const QueueItem = ({ className, ...props }: QueueItemProps) => (
+
+);
+
+export type QueueItemIndicatorProps = ComponentProps<"span"> & {
+ completed?: boolean;
+};
+
+export const QueueItemIndicator = ({
+ completed = false,
+ className,
+ ...props
+}: QueueItemIndicatorProps) => (
+
+);
+
+export type QueueItemContentProps = ComponentProps<"span"> & {
+ completed?: boolean;
+};
+
+export const QueueItemContent = ({
+ completed = false,
+ className,
+ ...props
+}: QueueItemContentProps) => (
+
+);
+
+export type QueueItemDescriptionProps = ComponentProps<"div"> & {
+ completed?: boolean;
+};
+
+export const QueueItemDescription = ({
+ completed = false,
+ className,
+ ...props
+}: QueueItemDescriptionProps) => (
+
+);
+
+export type QueueItemActionsProps = ComponentProps<"div">;
+
+export const QueueItemActions = ({
+ className,
+ ...props
+}: QueueItemActionsProps) => (
+
+);
+
+export type QueueItemActionProps = Omit<
+ ComponentProps,
+ "variant" | "size"
+>;
+
+export const QueueItemAction = ({
+ className,
+ ...props
+}: QueueItemActionProps) => (
+
+);
+
+export type QueueItemAttachmentProps = ComponentProps<"div">;
+
+export const QueueItemAttachment = ({
+ className,
+ ...props
+}: QueueItemAttachmentProps) => (
+
+);
+
+export type QueueItemImageProps = ComponentProps<"img">;
+
+export const QueueItemImage = ({
+ className,
+ ...props
+}: QueueItemImageProps) => (
+
+);
+
+export type QueueItemFileProps = ComponentProps<"span">;
+
+export const QueueItemFile = ({
+ children,
+ className,
+ ...props
+}: QueueItemFileProps) => (
+
+
+ {children}
+
+);
+
+export type QueueListProps = ComponentProps;
+
+export const QueueList = ({
+ children,
+ className,
+ ...props
+}: QueueListProps) => (
+
+
+
+);
+
+// QueueSection - collapsible section container
+export type QueueSectionProps = ComponentProps;
+
+export const QueueSection = ({
+ className,
+ defaultOpen = true,
+ ...props
+}: QueueSectionProps) => (
+
+);
+
+// QueueSectionTrigger - section header/trigger
+export type QueueSectionTriggerProps = ComponentProps<"button">;
+
+export const QueueSectionTrigger = ({
+ children,
+ className,
+ ...props
+}: QueueSectionTriggerProps) => (
+
+
+ {children}
+
+
+);
+
+// QueueSectionLabel - label content with icon and count
+export type QueueSectionLabelProps = ComponentProps<"span"> & {
+ count?: number;
+ label: string;
+ icon?: React.ReactNode;
+};
+
+export const QueueSectionLabel = ({
+ count,
+ label,
+ icon,
+ className,
+ ...props
+}: QueueSectionLabelProps) => (
+
+
+ {icon}
+
+ {count} {label}
+
+
+);
+
+// QueueSectionContent - collapsible content area
+export type QueueSectionContentProps = ComponentProps<
+ typeof CollapsibleContent
+>;
+
+export const QueueSectionContent = ({
+ className,
+ ...props
+}: QueueSectionContentProps) => (
+
+);
+
+export type QueueProps = ComponentProps<"div">;
+
+export const Queue = ({ className, ...props }: QueueProps) => (
+
+);
diff --git a/web/components/ai-elements/reasoning.spec.tsx b/web/components/ai-elements/reasoning.spec.tsx
new file mode 100644
index 0000000..4785116
--- /dev/null
+++ b/web/components/ai-elements/reasoning.spec.tsx
@@ -0,0 +1,43 @@
+import React from "react";
+import { render, screen } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+
+jest.mock("streamdown", () => ({
+ Streamdown: ({ children }: { children: React.ReactNode }) => (
+ {children}
+ ),
+}));
+
+import {
+ Reasoning,
+ ReasoningContent,
+ ReasoningTrigger,
+} from "./reasoning";
+
+describe("Reasoning", () => {
+ it("shows a thinking indicator while streaming", () => {
+ render(
+
+
+ Inline reasoning
+
+ );
+
+ expect(screen.getByText("Thinking...")).toBeInTheDocument();
+ });
+
+ it("displays duration-aware messaging after completion", async () => {
+ render(
+
+
+ Finished content
+
+ );
+
+ expect(
+ screen.getByText(text => text.includes("Thought for 5 seconds"))
+ ).toBeInTheDocument();
+ await userEvent.click(screen.getByRole("button"));
+ expect(screen.getByText("Finished content")).toBeInTheDocument();
+ });
+});
diff --git a/web/components/ai-elements/reasoning.stories.tsx b/web/components/ai-elements/reasoning.stories.tsx
new file mode 100644
index 0000000..60aed2d
--- /dev/null
+++ b/web/components/ai-elements/reasoning.stories.tsx
@@ -0,0 +1,103 @@
+import type { Meta, StoryObj } from "@storybook/react-vite";
+
+import { ChainOfThought, ChainOfThoughtContent, ChainOfThoughtHeader, ChainOfThoughtImage, ChainOfThoughtSearchResult, ChainOfThoughtSearchResults, ChainOfThoughtStep } from "./chain-of-thought";
+import { InlineCitation, InlineCitationCard, InlineCitationCardBody, InlineCitationCardTrigger, InlineCitationCarousel, InlineCitationCarouselContent, InlineCitationCarouselHeader, InlineCitationCarouselIndex, InlineCitationCarouselItem, InlineCitationCarouselNext, InlineCitationCarouselPrev, InlineCitationText } from "./inline-citation";
+import { Loader } from "./loader";
+import { Reasoning, ReasoningContent, ReasoningTrigger } from "./reasoning";
+import { Shimmer } from "./shimmer";
+import { Source, Sources, SourcesContent, SourcesTrigger } from "./sources";
+
+const meta = {
+ title: "AI Elements/Reasoning",
+ component: Reasoning,
+ tags: ["autodocs"],
+ parameters: {
+ layout: "centered",
+ },
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+export const Streaming: Story = {
+ render: () => (
+
+
+
+ {"The assistant is evaluating available sources and outlining next steps."}
+
+
+
+ How the answer was formed
+
+
+
+ Auth guide
+ Webhook docs
+
+
+
+
+
+ Placeholder chart
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ This recommendation is grounded in the latest docs.
+
+
+
+
+
+
+
+
+
+
+
+
+
+ example.com — Updated rate limits section.
+
+
+
+
+ example.com/blog — Launch announcement and migration tips.
+
+
+
+
+
+
+
+
+
+
+
+ Waiting for model output…
+
+
+
+ ),
+};
diff --git a/web/components/ai-elements/reasoning.tsx b/web/components/ai-elements/reasoning.tsx
new file mode 100644
index 0000000..a8ac802
--- /dev/null
+++ b/web/components/ai-elements/reasoning.tsx
@@ -0,0 +1,180 @@
+"use client";
+
+import { useControllableState } from "@radix-ui/react-use-controllable-state";
+import {
+ Collapsible,
+ CollapsibleContent,
+ CollapsibleTrigger,
+} from "@/components/ui/collapsible";
+import { cn } from "@/lib/utils";
+import { BrainIcon, ChevronDownIcon } from "lucide-react";
+import type { ComponentProps, ReactNode } from "react";
+import { createContext, memo, useContext, useEffect, useRef, useState } from "react";
+import { Streamdown } from "streamdown";
+import { Shimmer } from "./shimmer";
+
+type ReasoningContextValue = {
+ isStreaming: boolean;
+ isOpen: boolean;
+ setIsOpen: (open: boolean) => void;
+ duration: number | undefined;
+};
+
+const ReasoningContext = createContext(null);
+
+export const useReasoning = () => {
+ const context = useContext(ReasoningContext);
+ if (!context) {
+ throw new Error("Reasoning components must be used within Reasoning");
+ }
+ return context;
+};
+
+export type ReasoningProps = ComponentProps & {
+ isStreaming?: boolean;
+ open?: boolean;
+ defaultOpen?: boolean;
+ onOpenChange?: (open: boolean) => void;
+ duration?: number;
+};
+
+const AUTO_CLOSE_DELAY = 1000;
+const MS_IN_S = 1000;
+
+export const Reasoning = memo(
+ ({
+ className,
+ isStreaming = false,
+ open,
+ defaultOpen = true,
+ onOpenChange,
+ duration: durationProp,
+ children,
+ ...props
+ }: ReasoningProps) => {
+ const [isOpen, setIsOpen] = useControllableState({
+ prop: open,
+ defaultProp: defaultOpen,
+ onChange: onOpenChange,
+ });
+ const [duration, setDuration] = useControllableState({
+ prop: durationProp,
+ defaultProp: undefined,
+ });
+
+ const [hasAutoClosed, setHasAutoClosed] = useState(false);
+ const startTimeRef = useRef(null);
+
+ // Track duration when streaming starts and ends
+ useEffect(() => {
+ if (isStreaming) {
+ if (startTimeRef.current === null) {
+ startTimeRef.current = Date.now();
+ }
+ } else if (startTimeRef.current !== null) {
+ setDuration(Math.ceil((Date.now() - startTimeRef.current) / MS_IN_S));
+ startTimeRef.current = null;
+ }
+ }, [isStreaming, setDuration]);
+
+ // Auto-open when streaming starts, auto-close when streaming ends (once only)
+ useEffect(() => {
+ if (defaultOpen && !isStreaming && isOpen && !hasAutoClosed) {
+ // Add a small delay before closing to allow user to see the content
+ const timer = setTimeout(() => {
+ setIsOpen(false);
+ setHasAutoClosed(true);
+ }, AUTO_CLOSE_DELAY);
+
+ return () => clearTimeout(timer);
+ }
+ }, [isStreaming, isOpen, defaultOpen, setIsOpen, hasAutoClosed]);
+
+ const handleOpenChange = (newOpen: boolean) => {
+ setIsOpen(newOpen);
+ };
+
+ return (
+
+
+ {children}
+
+
+ );
+ }
+);
+
+export type ReasoningTriggerProps = ComponentProps & {
+ getThinkingMessage?: (isStreaming: boolean, duration?: number) => ReactNode;
+};
+
+const defaultGetThinkingMessage = (isStreaming: boolean, duration?: number) => {
+ if (isStreaming || duration === 0) {
+ return Thinking... ;
+ }
+ if (duration === undefined) {
+ return Thought for a few seconds
;
+ }
+ return Thought for {duration} seconds
;
+};
+
+export const ReasoningTrigger = memo(
+ ({ className, children, getThinkingMessage = defaultGetThinkingMessage, ...props }: ReasoningTriggerProps) => {
+ const { isStreaming, isOpen, duration } = useReasoning();
+
+ return (
+
+ {children ?? (
+ <>
+
+ {getThinkingMessage(isStreaming, duration)}
+
+ >
+ )}
+
+ );
+ }
+);
+
+export type ReasoningContentProps = ComponentProps<
+ typeof CollapsibleContent
+> & {
+ children: string;
+};
+
+export const ReasoningContent = memo(
+ ({ className, children, ...props }: ReasoningContentProps) => (
+
+ {children}
+
+ )
+);
+
+Reasoning.displayName = "Reasoning";
+ReasoningTrigger.displayName = "ReasoningTrigger";
+ReasoningContent.displayName = "ReasoningContent";
diff --git a/web/components/ai-elements/shimmer.spec.tsx b/web/components/ai-elements/shimmer.spec.tsx
new file mode 100644
index 0000000..9d43774
--- /dev/null
+++ b/web/components/ai-elements/shimmer.spec.tsx
@@ -0,0 +1,67 @@
+import { render, screen } from "@testing-library/react";
+import React from "react";
+import type { ComponentType, ForwardRefRenderFunction } from "react";
+
+const renderedProps: Array> = [];
+
+jest.mock("motion/react", () => {
+ const MockSpan = React.forwardRef<
+ HTMLSpanElement,
+ Record
+ >((props, ref) => {
+ renderedProps.push(props);
+ return React.createElement("span", { ref, ...props });
+ });
+ MockSpan.displayName = "MotionSpanMock";
+
+ return {
+ __esModule: true,
+ motion: {
+ span: MockSpan,
+ create: (Component: keyof JSX.IntrinsicElements) => {
+ const Wrapper: ForwardRefRenderFunction<
+ HTMLElement,
+ Record
+ > = (props, ref) => {
+ renderedProps.push(props);
+ return React.createElement(Component as ComponentType, {
+ ref,
+ ...props,
+ });
+ };
+
+ Wrapper.displayName = "MotionCreateMock";
+
+ return React.forwardRef(Wrapper);
+ },
+ },
+ };
+});
+
+import { Shimmer } from "./shimmer";
+
+describe("Shimmer", () => {
+ beforeEach(() => {
+ renderedProps.length = 0;
+ });
+
+ it("applies animated gradient styles and respects custom spread", () => {
+ render(
+
+ Hello
+
+ );
+
+ const element = screen.getByText("Hello");
+ expect(element).toHaveClass("custom");
+ expect(element.style.getPropertyValue("--spread")).toBe("15px");
+
+ const props = renderedProps[0];
+ const transition = props?.transition as
+ | Record
+ | undefined;
+
+ expect(props?.animate).toEqual({ backgroundPosition: "0% center" });
+ expect(transition?.duration).toBe(3);
+ });
+});
diff --git a/web/components/ai-elements/shimmer.tsx b/web/components/ai-elements/shimmer.tsx
new file mode 100644
index 0000000..77bb686
--- /dev/null
+++ b/web/components/ai-elements/shimmer.tsx
@@ -0,0 +1,64 @@
+"use client";
+
+import { cn } from "@/lib/utils";
+import { motion } from "motion/react";
+import { type CSSProperties, type ElementType, memo, useMemo } from "react";
+
+export type TextShimmerProps = {
+ children: string;
+ as?: ElementType;
+ className?: string;
+ duration?: number;
+ spread?: number;
+};
+
+const motionComponents = {
+ p: motion.p,
+ span: motion.span,
+ div: motion.div,
+};
+
+const ShimmerComponent = ({
+ children,
+ as: Component = "p",
+ className,
+ duration = 2,
+ spread = 2,
+}: TextShimmerProps) => {
+ const MotionComponent =
+ typeof Component === "string" && Component in motionComponents
+ ? motionComponents[Component as keyof typeof motionComponents]
+ : motionComponents.span;
+ const dynamicSpread = useMemo(
+ () => (children?.length ?? 0) * spread,
+ [children, spread]
+ );
+
+ return (
+
+ {children}
+
+ );
+};
+
+export const Shimmer = memo(ShimmerComponent);
diff --git a/web/components/ai-elements/sources.spec.tsx b/web/components/ai-elements/sources.spec.tsx
new file mode 100644
index 0000000..316cbd5
--- /dev/null
+++ b/web/components/ai-elements/sources.spec.tsx
@@ -0,0 +1,55 @@
+import { render, screen } from "@testing-library/react";
+
+jest.mock("@/components/ui/collapsible", () => ({
+ __esModule: true,
+ Collapsible: ({
+ children,
+ ...props
+ }: React.ComponentProps<"div">) => (
+
+ {children}
+
+ ),
+ CollapsibleContent: ({
+ children,
+ ...props
+ }: React.ComponentProps<"div">) => (
+
+ {children}
+
+ ),
+ CollapsibleTrigger: ({
+ children,
+ ...props
+ }: React.ComponentProps<"button">) => (
+
+ {children}
+
+ ),
+}));
+
+import { Source, Sources, SourcesContent, SourcesTrigger } from "./sources";
+
+describe("Sources", () => {
+ it("renders a collapsible list with trigger text", () => {
+ render(
+
+
+
+
+
+
+ );
+
+ expect(screen.getByText("Used 3 sources")).toBeInTheDocument();
+ expect(screen.getByText("Example")).toBeInTheDocument();
+ });
+
+ it("renders links that open in a new tab", () => {
+ render( );
+
+ const link = screen.getByRole("link", { name: "Docs" });
+ expect(link).toHaveAttribute("target", "_blank");
+ expect(link).toHaveAttribute("rel", "noreferrer");
+ });
+});
diff --git a/web/components/ai-elements/sources.tsx b/web/components/ai-elements/sources.tsx
new file mode 100644
index 0000000..0756664
--- /dev/null
+++ b/web/components/ai-elements/sources.tsx
@@ -0,0 +1,77 @@
+"use client";
+
+import {
+ Collapsible,
+ CollapsibleContent,
+ CollapsibleTrigger,
+} from "@/components/ui/collapsible";
+import { cn } from "@/lib/utils";
+import { BookIcon, ChevronDownIcon } from "lucide-react";
+import type { ComponentProps } from "react";
+
+export type SourcesProps = ComponentProps<"div">;
+
+export const Sources = ({ className, ...props }: SourcesProps) => (
+
+);
+
+export type SourcesTriggerProps = ComponentProps & {
+ count: number;
+};
+
+export const SourcesTrigger = ({
+ className,
+ count,
+ children,
+ ...props
+}: SourcesTriggerProps) => (
+
+ {children ?? (
+ <>
+ Used {count} sources
+
+ >
+ )}
+
+);
+
+export type SourcesContentProps = ComponentProps;
+
+export const SourcesContent = ({
+ className,
+ ...props
+}: SourcesContentProps) => (
+
+);
+
+export type SourceProps = ComponentProps<"a">;
+
+export const Source = ({ href, title, children, ...props }: SourceProps) => (
+
+ {children ?? (
+ <>
+
+ {title}
+ >
+ )}
+
+);
diff --git a/web/components/ai-elements/suggestion-and-queue.spec.tsx b/web/components/ai-elements/suggestion-and-queue.spec.tsx
new file mode 100644
index 0000000..437d170
--- /dev/null
+++ b/web/components/ai-elements/suggestion-and-queue.spec.tsx
@@ -0,0 +1,63 @@
+import { render, screen, waitFor } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+
+import {
+ Queue,
+ QueueItem,
+ QueueItemContent,
+ QueueItemDescription,
+ QueueItemIndicator,
+ QueueList,
+ QueueSection,
+ QueueSectionContent,
+ QueueSectionLabel,
+ QueueSectionTrigger,
+} from "./queue";
+import { Suggestion, Suggestions } from "./suggestion";
+
+describe("Suggestion", () => {
+ it("calls onClick with the suggestion text", async () => {
+ const onClick = jest.fn();
+ render(
+
+
+
+ );
+
+ await userEvent.click(screen.getByRole("button", { name: "Try another prompt" }));
+ expect(onClick).toHaveBeenCalledWith("Try another prompt");
+ });
+});
+
+describe("Queue", () => {
+ it("expands sections and renders queue items", async () => {
+ render(
+
+
+
+
+
+
+
+
+
+ Summarize the document
+
+ Use the latest attachment for details.
+
+
+
+
+
+
+ );
+
+ expect(screen.queryByText("Summarize the document")).toBeNull();
+ await userEvent.click(screen.getByRole("button"));
+
+ await waitFor(() => {
+ expect(screen.getByText("Summarize the document")).toBeVisible();
+ expect(screen.getByText("Use the latest attachment for details.")).toBeVisible();
+ });
+ });
+});
diff --git a/web/components/ai-elements/suggestion.spec.tsx b/web/components/ai-elements/suggestion.spec.tsx
new file mode 100644
index 0000000..d402f68
--- /dev/null
+++ b/web/components/ai-elements/suggestion.spec.tsx
@@ -0,0 +1,46 @@
+import { render, screen } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+
+jest.mock("@/components/ui/scroll-area", () => ({
+ __esModule: true,
+ ScrollArea: ({
+ children,
+ ...props
+ }: React.ComponentProps<"div">) => (
+
+ {children}
+
+ ),
+ ScrollBar: ({
+ children,
+ ...props
+ }: React.ComponentProps<"div">) => (
+
+ {children}
+
+ ),
+}));
+
+import { Suggestion, Suggestions } from "./suggestion";
+
+describe("Suggestion", () => {
+ it("renders suggestion chips inside a scrollable row", () => {
+ render(
+
+
+
+
+ );
+
+ expect(screen.getAllByRole("button")).toHaveLength(2);
+ expect(screen.getByTestId("scroll-bar")).toBeInTheDocument();
+ });
+
+ it("invokes handler with the provided suggestion text", async () => {
+ const onClick = jest.fn();
+ render( );
+
+ await userEvent.click(screen.getByRole("button", { name: "Try this" }));
+ expect(onClick).toHaveBeenCalledWith("Try this");
+ });
+});
diff --git a/web/components/ai-elements/suggestion.tsx b/web/components/ai-elements/suggestion.tsx
new file mode 100644
index 0000000..9d76a82
--- /dev/null
+++ b/web/components/ai-elements/suggestion.tsx
@@ -0,0 +1,56 @@
+"use client";
+
+import { Button } from "@/components/ui/button";
+import {
+ ScrollArea,
+ ScrollBar,
+} from "@/components/ui/scroll-area";
+import { cn } from "@/lib/utils";
+import type { ComponentProps } from "react";
+
+export type SuggestionsProps = ComponentProps;
+
+export const Suggestions = ({
+ className,
+ children,
+ ...props
+}: SuggestionsProps) => (
+
+
+ {children}
+
+
+
+);
+
+export type SuggestionProps = Omit, "onClick"> & {
+ suggestion: string;
+ onClick?: (suggestion: string) => void;
+};
+
+export const Suggestion = ({
+ suggestion,
+ onClick,
+ className,
+ variant = "outline",
+ size = "sm",
+ children,
+ ...props
+}: SuggestionProps) => {
+ const handleClick = () => {
+ onClick?.(suggestion);
+ };
+
+ return (
+
+ {children || suggestion}
+
+ );
+};
diff --git a/web/components/ai-elements/task.tsx b/web/components/ai-elements/task.tsx
new file mode 100644
index 0000000..eeb802c
--- /dev/null
+++ b/web/components/ai-elements/task.tsx
@@ -0,0 +1,87 @@
+"use client";
+
+import {
+ Collapsible,
+ CollapsibleContent,
+ CollapsibleTrigger,
+} from "@/components/ui/collapsible";
+import { cn } from "@/lib/utils";
+import { ChevronDownIcon, SearchIcon } from "lucide-react";
+import type { ComponentProps } from "react";
+
+export type TaskItemFileProps = ComponentProps<"div">;
+
+export const TaskItemFile = ({
+ children,
+ className,
+ ...props
+}: TaskItemFileProps) => (
+
+ {children}
+
+);
+
+export type TaskItemProps = ComponentProps<"div">;
+
+export const TaskItem = ({ children, className, ...props }: TaskItemProps) => (
+
+ {children}
+
+);
+
+export type TaskProps = ComponentProps;
+
+export const Task = ({
+ defaultOpen = true,
+ className,
+ ...props
+}: TaskProps) => (
+
+);
+
+export type TaskTriggerProps = ComponentProps & {
+ title: string;
+};
+
+export const TaskTrigger = ({
+ children,
+ className,
+ title,
+ ...props
+}: TaskTriggerProps) => (
+
+ {children ?? (
+
+ )}
+
+);
+
+export type TaskContentProps = ComponentProps;
+
+export const TaskContent = ({
+ children,
+ className,
+ ...props
+}: TaskContentProps) => (
+
+
+ {children}
+
+
+);
diff --git a/web/components/ai-elements/tool-and-task.spec.tsx b/web/components/ai-elements/tool-and-task.spec.tsx
new file mode 100644
index 0000000..fdf1922
--- /dev/null
+++ b/web/components/ai-elements/tool-and-task.spec.tsx
@@ -0,0 +1,67 @@
+import { render, screen } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+
+import {
+ Task,
+ TaskContent,
+ TaskItem,
+ TaskItemFile,
+ TaskTrigger,
+} from "./task";
+import {
+ Tool,
+ ToolContent,
+ ToolHeader,
+ ToolInput,
+ ToolOutput,
+} from "./tool";
+
+jest.mock("./code-block", () => ({
+ CodeBlock: ({ code }: { code: string }) => (
+ {code}
+ ),
+}));
+
+describe("Tool", () => {
+ it("renders tool header, input, and output", async () => {
+ render(
+
+
+
+
+
+
+
+ );
+
+ const trigger = screen.getByRole("button", { name: /Search/ });
+ expect(trigger).toBeInTheDocument();
+ await userEvent.click(trigger);
+
+ const codeBlocks = await screen.findAllByTestId("code-block");
+ expect(codeBlocks[0]).toHaveTextContent(/latest results/);
+ expect(codeBlocks[1]).toHaveTextContent(/success/);
+ });
+});
+
+describe("Task", () => {
+ it("toggles task content visibility", async () => {
+ render(
+
+
+
+
+ Read spec.pdf and summarize it.
+
+
+
+ );
+
+ const trigger = screen.getByText("Review docs").closest(
+ '[data-slot="collapsible-trigger"]'
+ ) as HTMLElement;
+
+ await userEvent.click(trigger);
+ expect(await screen.findByText(/spec\.pdf/)).toBeVisible();
+ });
+});
diff --git a/web/components/ai-elements/tool.tsx b/web/components/ai-elements/tool.tsx
new file mode 100644
index 0000000..dfdd31d
--- /dev/null
+++ b/web/components/ai-elements/tool.tsx
@@ -0,0 +1,165 @@
+"use client";
+
+import { Badge } from "@/components/ui/badge";
+import {
+ Collapsible,
+ CollapsibleContent,
+ CollapsibleTrigger,
+} from "@/components/ui/collapsible";
+import { cn } from "@/lib/utils";
+import type { ToolUIPart } from "ai";
+import {
+ CheckCircleIcon,
+ ChevronDownIcon,
+ CircleIcon,
+ ClockIcon,
+ WrenchIcon,
+ XCircleIcon,
+} from "lucide-react";
+import type { ComponentProps, ReactNode } from "react";
+import { isValidElement } from "react";
+import { CodeBlock } from "./code-block";
+
+export type ToolProps = ComponentProps;
+
+export const Tool = ({ className, ...props }: ToolProps) => (
+
+);
+
+export type ToolHeaderProps = {
+ title?: string;
+ type: ToolUIPart["type"];
+ state: ToolUIPart["state"];
+ className?: string;
+};
+
+const getStatusBadge = (status: ToolUIPart["state"]) => {
+ const labels: Record = {
+ "input-streaming": "Pending",
+ "input-available": "Running",
+ // @ts-expect-error state only available in AI SDK v6
+ "approval-requested": "Awaiting Approval",
+ "approval-responded": "Responded",
+ "output-available": "Completed",
+ "output-error": "Error",
+ "output-denied": "Denied",
+ };
+
+ const icons: Record = {
+ "input-streaming": ,
+ "input-available": ,
+ // @ts-expect-error state only available in AI SDK v6
+ "approval-requested": ,
+ "approval-responded": ,
+ "output-available": ,
+ "output-error": ,
+ "output-denied": ,
+ };
+
+ return (
+
+ {icons[status]}
+ {labels[status]}
+
+ );
+};
+
+export const ToolHeader = ({
+ className,
+ title,
+ type,
+ state,
+ ...props
+}: ToolHeaderProps) => (
+
+
+
+
+ {title ?? type.split("-").slice(1).join("-")}
+
+ {getStatusBadge(state)}
+
+
+
+);
+
+export type ToolContentProps = ComponentProps;
+
+export const ToolContent = ({ className, ...props }: ToolContentProps) => (
+
+);
+
+export type ToolInputProps = ComponentProps<"div"> & {
+ input: ToolUIPart["input"];
+};
+
+export const ToolInput = ({ className, input, ...props }: ToolInputProps) => (
+
+);
+
+export type ToolOutputProps = ComponentProps<"div"> & {
+ output: ToolUIPart["output"];
+ errorText: ToolUIPart["errorText"];
+};
+
+export const ToolOutput = ({
+ className,
+ output,
+ errorText,
+ ...props
+}: ToolOutputProps) => {
+ if (!(output || errorText)) {
+ return null;
+ }
+
+ let Output = {output as ReactNode}
;
+
+ if (typeof output === "object" && !isValidElement(output)) {
+ Output = (
+
+ );
+ } else if (typeof output === "string") {
+ Output = ;
+ }
+
+ return (
+
+
+ {errorText ? "Error" : "Result"}
+
+
+ {errorText &&
{errorText}
}
+ {Output}
+
+
+ );
+};
diff --git a/web/components/ai-elements/toolbar.spec.tsx b/web/components/ai-elements/toolbar.spec.tsx
new file mode 100644
index 0000000..cfe88ed
--- /dev/null
+++ b/web/components/ai-elements/toolbar.spec.tsx
@@ -0,0 +1,41 @@
+import { render, screen } from "@testing-library/react";
+import type { ComponentProps } from "react";
+import type { NodeToolbarProps } from "@xyflow/react";
+
+const mockToolbar = jest.fn(
+ ({ children, ...props }: NodeToolbarProps & ComponentProps<"div">) => (
+
+ {children}
+
+ )
+);
+
+jest.mock("@xyflow/react", () => ({
+ __esModule: true,
+ NodeToolbar: (props: NodeToolbarProps & ComponentProps<"div">) =>
+ mockToolbar(props),
+ Position: { Bottom: "bottom" },
+}));
+
+import { Toolbar } from "./toolbar";
+
+describe("Toolbar", () => {
+ beforeEach(() => mockToolbar.mockClear());
+
+ it("renders node toolbar with bottom positioning and custom props", () => {
+ render(
+
+ Actions
+
+ );
+
+ expect(screen.getByTestId("node-toolbar")).toHaveTextContent("Actions");
+ expect(mockToolbar).toHaveBeenCalledWith(
+ expect.objectContaining({
+ position: "bottom",
+ className: expect.stringContaining("custom-toolbar"),
+ "data-id": "toolbar",
+ })
+ );
+ });
+});
diff --git a/web/components/ai-elements/toolbar.tsx b/web/components/ai-elements/toolbar.tsx
new file mode 100644
index 0000000..b55aa88
--- /dev/null
+++ b/web/components/ai-elements/toolbar.tsx
@@ -0,0 +1,16 @@
+import { cn } from "@/lib/utils";
+import { NodeToolbar, Position } from "@xyflow/react";
+import type { ComponentProps } from "react";
+
+type ToolbarProps = ComponentProps;
+
+export const Toolbar = ({ className, ...props }: ToolbarProps) => (
+
+);
diff --git a/web/components/ai-elements/web-preview.spec.tsx b/web/components/ai-elements/web-preview.spec.tsx
new file mode 100644
index 0000000..a184a93
--- /dev/null
+++ b/web/components/ai-elements/web-preview.spec.tsx
@@ -0,0 +1,65 @@
+import { render, screen, waitFor } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+
+const fetchMock = jest
+ .spyOn(global, "fetch")
+ .mockImplementation(() => Promise.resolve(new Response("ok")));
+const consoleErrorMock = jest
+ .spyOn(console, "error")
+ .mockImplementation(() => {});
+
+afterAll(() => {
+ fetchMock.mockRestore();
+ consoleErrorMock.mockRestore();
+});
+
+import {
+ WebPreview,
+ WebPreviewBody,
+ WebPreviewConsole,
+ WebPreviewNavigation,
+ WebPreviewUrl,
+} from "./web-preview";
+
+describe("WebPreview", () => {
+ it("updates the iframe source when a new URL is submitted", async () => {
+ render(
+
+
+
+
+
+
+ );
+
+ const urlInput = screen.getByPlaceholderText("Enter URL...");
+ await userEvent.clear(urlInput);
+ await userEvent.type(urlInput, "https://example.dev{enter}");
+
+ await waitFor(() => {
+ expect(screen.getByTestId("preview-frame")).toHaveAttribute(
+ "src",
+ "https://example.dev"
+ );
+ });
+ });
+
+ it("shows console output when expanded", async () => {
+ render(
+
+
+
+ );
+
+ await userEvent.click(screen.getByRole("button", { name: "Console" }));
+ expect(await screen.findByText("Check the network tab")).toBeInTheDocument();
+ });
+});
diff --git a/web/components/ai-elements/web-preview.stories.tsx b/web/components/ai-elements/web-preview.stories.tsx
new file mode 100644
index 0000000..afacdf0
--- /dev/null
+++ b/web/components/ai-elements/web-preview.stories.tsx
@@ -0,0 +1,58 @@
+import type { Meta, StoryObj } from "@storybook/react-vite";
+
+import {
+ WebPreview,
+ WebPreviewBody,
+ WebPreviewConsole,
+ WebPreviewNavigation,
+ WebPreviewNavigationButton,
+ WebPreviewUrl,
+} from "./web-preview";
+
+const meta = {
+ title: "AI Elements/Web Preview",
+ component: WebPreview,
+ tags: ["autodocs"],
+ parameters: {
+ layout: "fullscreen",
+ },
+ args: {
+ defaultUrl:
+ "data:text/html;charset=utf-8," +
+ encodeURIComponent(
+ "AI Preview This sandboxed iframe renders without external requests.
"
+ ),
+ },
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+export const Playground: Story = {
+ render: args => (
+
+
+
+
+ ←
+
+
+ →
+
+
+
+
+
+
+
+ ),
+};
diff --git a/web/components/ai-elements/web-preview.tsx b/web/components/ai-elements/web-preview.tsx
new file mode 100644
index 0000000..0b1c805
--- /dev/null
+++ b/web/components/ai-elements/web-preview.tsx
@@ -0,0 +1,251 @@
+"use client";
+
+import { Button } from "@/components/ui/button";
+import {
+ Collapsible,
+ CollapsibleContent,
+ CollapsibleTrigger,
+} from "@/components/ui/collapsible";
+import { Input } from "@/components/ui/input";
+import { Tooltip } from "@/components/ui/tooltip";
+import { cn } from "@/lib/utils";
+import { ChevronDownIcon } from "lucide-react";
+import type { ComponentProps, ReactNode } from "react";
+import { createContext, useContext, useEffect, useState } from "react";
+
+export type WebPreviewContextValue = {
+ url: string;
+ setUrl: (url: string) => void;
+ consoleOpen: boolean;
+ setConsoleOpen: (open: boolean) => void;
+};
+
+const WebPreviewContext = createContext(null);
+
+const useWebPreview = () => {
+ const context = useContext(WebPreviewContext);
+ if (!context) {
+ throw new Error("WebPreview components must be used within a WebPreview");
+ }
+ return context;
+};
+
+export type WebPreviewProps = ComponentProps<"div"> & {
+ defaultUrl?: string;
+ onUrlChange?: (url: string) => void;
+};
+
+export const WebPreview = ({
+ className,
+ children,
+ defaultUrl = "",
+ onUrlChange,
+ ...props
+}: WebPreviewProps) => {
+ const [url, setUrl] = useState(defaultUrl);
+ const [consoleOpen, setConsoleOpen] = useState(false);
+
+ const handleUrlChange = (newUrl: string) => {
+ setUrl(newUrl);
+ onUrlChange?.(newUrl);
+ };
+
+ const contextValue: WebPreviewContextValue = {
+ url,
+ setUrl: handleUrlChange,
+ consoleOpen,
+ setConsoleOpen,
+ };
+
+ return (
+
+
+ {children}
+
+
+ );
+};
+
+export type WebPreviewNavigationProps = ComponentProps<"div">;
+
+export const WebPreviewNavigation = ({
+ className,
+ children,
+ ...props
+}: WebPreviewNavigationProps) => (
+
+ {children}
+
+);
+
+export type WebPreviewNavigationButtonProps = ComponentProps & {
+ tooltip?: string;
+};
+
+export const WebPreviewNavigationButton = ({
+ onClick,
+ disabled,
+ tooltip,
+ children,
+ ...props
+}: WebPreviewNavigationButtonProps) => (
+
+
+ {children}
+
+
+);
+
+export type WebPreviewUrlProps = ComponentProps;
+
+export const WebPreviewUrl = ({
+ value,
+ onChange,
+ onKeyDown,
+ ...props
+}: WebPreviewUrlProps) => {
+ const { url, setUrl } = useWebPreview();
+ const [inputValue, setInputValue] = useState(url);
+
+ // Sync input value with context URL when it changes externally
+ useEffect(() => {
+ setInputValue(url);
+ }, [url]);
+
+ const handleChange = (event: React.ChangeEvent) => {
+ setInputValue(event.target.value);
+ onChange?.(event);
+ };
+
+ const handleKeyDown = (event: React.KeyboardEvent) => {
+ if (event.key === "Enter") {
+ const target = event.target as HTMLInputElement;
+ setUrl(target.value);
+ }
+ onKeyDown?.(event);
+ };
+
+ return (
+
+ );
+};
+
+export type WebPreviewBodyProps = ComponentProps<"iframe"> & {
+ loading?: ReactNode;
+};
+
+export const WebPreviewBody = ({
+ className,
+ loading,
+ src,
+ ...props
+}: WebPreviewBodyProps) => {
+ const { url } = useWebPreview();
+
+ return (
+
+
+ {loading}
+
+ );
+};
+
+export type WebPreviewConsoleProps = ComponentProps<"div"> & {
+ logs?: Array<{
+ level: "log" | "warn" | "error";
+ message: string;
+ timestamp: Date;
+ }>;
+};
+
+export const WebPreviewConsole = ({
+ className,
+ logs = [],
+ children,
+ ...props
+}: WebPreviewConsoleProps) => {
+ const { consoleOpen, setConsoleOpen } = useWebPreview();
+
+ return (
+
+
+
+ Console
+
+
+
+
+
+ {logs.length === 0 ? (
+
No console output
+ ) : (
+ logs.map((log, index) => (
+
+
+ {log.timestamp.toLocaleTimeString()}
+ {" "}
+ {log.message}
+
+ ))
+ )}
+ {children}
+
+
+
+ );
+};
diff --git a/web/components/ai-elements/workflow.stories.tsx b/web/components/ai-elements/workflow.stories.tsx
new file mode 100644
index 0000000..5fb5d78
--- /dev/null
+++ b/web/components/ai-elements/workflow.stories.tsx
@@ -0,0 +1,112 @@
+import type { Meta, StoryObj } from "@storybook/react-vite";
+
+import { Checkpoint, CheckpointIcon, CheckpointTrigger } from "./checkpoint";
+import {
+ Plan,
+ PlanAction,
+ PlanContent,
+ PlanDescription,
+ PlanFooter,
+ PlanHeader,
+ PlanTitle,
+ PlanTrigger,
+} from "./plan";
+import {
+ Queue,
+ QueueItem,
+ QueueItemContent,
+ QueueItemDescription,
+ QueueItemIndicator,
+ QueueList,
+ QueueSection,
+ QueueSectionContent,
+ QueueSectionLabel,
+ QueueSectionTrigger,
+} from "./queue";
+import { Task, TaskContent, TaskItem, TaskItemFile, TaskTrigger } from "./task";
+
+const meta = {
+ title: "AI Elements/Workflow",
+ component: Queue,
+ tags: ["autodocs"],
+ parameters: {
+ layout: "centered",
+ },
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+export const PlanningAndQueue: Story = {
+ render: () => (
+
+
+
+
+
Prepare onboarding kit
+
+ Outline assets and checkpoints for the new hire flow.
+
+
+
+
+
+
+ Draft welcome email copy
+ Collect links to handbooks and SOPs
+ Schedule kickoff meeting
+
+
+
+
+
+
+ 1 of 3 checkpoints saved
+ + Add
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Summarize yesterday's standup
+
+ Provide a 3-bullet summary with blockers.
+
+
+
+
+
+ Translate product update into Spanish
+
+
+
+
+
+
+
+
+
+
+
+ Found two relevant files: pricing.xlsx and{" "}
+ faq.pdf
+
+
+ Next step: align messaging with updated pricing tiers.
+
+
+
+
+
+ ),
+};
diff --git a/web/components/ui/alert/index.spec.tsx b/web/components/ui/alert/index.spec.tsx
new file mode 100644
index 0000000..5037eb2
--- /dev/null
+++ b/web/components/ui/alert/index.spec.tsx
@@ -0,0 +1,59 @@
+import { render, screen } from "@testing-library/react";
+
+import "@testing-library/jest-dom";
+
+import { Alert, AlertDescription, AlertTitle } from "./index";
+
+describe("Alert", () => {
+ test("renders an accessible alert with title and description slots", () => {
+ render(
+
+ Heads up!
+ Use caution when proceeding.
+
+ );
+
+ const alert = screen.getByRole("alert");
+ expect(alert).toHaveAttribute("data-slot", "alert");
+ expect(alert).toHaveClass("custom-alert");
+ expect(alert.className).toContain("rounded-lg");
+
+ expect(screen.getByText("Heads up!")).toHaveAttribute(
+ "data-slot",
+ "alert-title"
+ );
+ expect(screen.getByText("Use caution when proceeding.")).toHaveAttribute(
+ "data-slot",
+ "alert-description"
+ );
+ });
+
+ test("applies the destructive variant styling", () => {
+ render(
+
+ System issue
+ Something went wrong.
+
+ );
+
+ const alert = screen.getByRole("alert");
+ expect(alert.className).toContain("text-destructive");
+ expect(alert.className).toContain("bg-card");
+ });
+
+ test("keeps title and description aligned to the content column", () => {
+ render(
+
+ Title
+ Details
+
+ );
+
+ const title = screen.getByText("Title");
+ const description = screen.getByText("Details");
+
+ expect(title.className).toContain("col-start-2");
+ expect(description.className).toContain("col-start-2");
+ expect(description.className).toContain("text-muted-foreground");
+ });
+});
diff --git a/web/components/ui/alert/index.stories.tsx b/web/components/ui/alert/index.stories.tsx
new file mode 100644
index 0000000..20a2e4d
--- /dev/null
+++ b/web/components/ui/alert/index.stories.tsx
@@ -0,0 +1,93 @@
+import type { Meta, StoryObj } from "@storybook/react-vite";
+import { AlertCircleIcon, CheckIcon, InfoIcon } from "lucide-react";
+
+import {
+ Alert,
+ AlertDescription,
+ AlertTitle,
+} from "@/components/ui/alert";
+
+const meta = {
+ title: "UI/Alert",
+ component: Alert,
+ tags: ["autodocs"],
+ parameters: {
+ layout: "centered",
+ },
+ argTypes: {
+ variant: {
+ control: "select",
+ options: ["default", "destructive"],
+ },
+ },
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+export const Default: Story = {
+ args: {
+ variant: "default",
+ },
+ render: args => (
+
+ Heads up
+
+ Use alerts to highlight contextual information without disrupting the
+ primary task.
+
+
+ ),
+};
+
+export const WithIcon: Story = {
+ render: args => (
+
+
+ New feature available
+
+ Discover the redesigned workspace switcher in the top navigation bar.
+
+
+ ),
+ args: {
+ variant: "default",
+ },
+};
+
+export const Destructive: Story = {
+ render: args => (
+
+
+ Action required
+
+ Billing failed for the last invoice. Update payment details to prevent
+ service interruption.
+
+
+ ),
+ args: {
+ variant: "destructive",
+ },
+};
+
+export const Checklist: Story = {
+ render: () => (
+
+
+
+ Environment configured
+
+ Connection to production database is healthy and up to date.
+
+
+
+
+ Deploy notice
+
+ Deployments run nightly at 02:00 UTC with automatic rollbacks.
+
+
+
+ ),
+};
diff --git a/web/components/ui/alert/index.tsx b/web/components/ui/alert/index.tsx
new file mode 100644
index 0000000..5b1a0b5
--- /dev/null
+++ b/web/components/ui/alert/index.tsx
@@ -0,0 +1,66 @@
+import * as React from "react";
+import { cva, type VariantProps } from "class-variance-authority";
+
+import { cn } from "@/lib/utils";
+
+const alertVariants = cva(
+ "relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current",
+ {
+ variants: {
+ variant: {
+ default: "bg-card text-card-foreground",
+ destructive:
+ "text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ },
+ }
+);
+
+function Alert({
+ className,
+ variant,
+ ...props
+}: React.ComponentProps<"div"> & VariantProps) {
+ return (
+
+ );
+}
+
+function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+function AlertDescription({
+ className,
+ ...props
+}: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+export { Alert, AlertTitle, AlertDescription };
diff --git a/web/components/ui/button-group/index.spec.tsx b/web/components/ui/button-group/index.spec.tsx
new file mode 100644
index 0000000..7013b93
--- /dev/null
+++ b/web/components/ui/button-group/index.spec.tsx
@@ -0,0 +1,70 @@
+import { render, screen } from "@testing-library/react";
+
+import "@testing-library/jest-dom";
+
+import {
+ ButtonGroup,
+ ButtonGroupSeparator,
+ ButtonGroupText,
+} from "./index";
+
+describe("ButtonGroup", () => {
+ test("renders a horizontal grouped control with role semantics", () => {
+ render(
+
+ Create
+ Edit
+
+ );
+
+ const group = screen.getByTestId("button-group");
+ expect(group).toHaveAttribute("role", "group");
+ expect(group).toHaveAttribute("data-slot", "button-group");
+ expect(group).toHaveAttribute("data-orientation", "horizontal");
+ expect(group.className).toContain("flex");
+ expect(group).toHaveClass("custom-group");
+ });
+
+ test("supports vertical orientation for stacked actions", () => {
+ render(
+
+ Top
+ Bottom
+
+ );
+
+ const group = screen.getByRole("group");
+ expect(group).toHaveAttribute("data-orientation", "vertical");
+ expect(group.className).toContain("flex-col");
+ });
+
+ test("forwards typography styles to child nodes when rendered asChild", () => {
+ render(
+
+ Schedule
+
+ );
+
+ const label = screen.getByTestId("label");
+ expect(label.className).toContain("bg-muted");
+ expect(label.className).toContain("rounded-md");
+ });
+
+ test("renders separators that inherit orientation styles", () => {
+ render(
+
+ One
+
+ Two
+
+ );
+
+ const separator = screen.getByTestId("separator");
+ expect(separator).toHaveAttribute("data-slot", "button-group-separator");
+ expect(separator.className).toContain("self-stretch");
+ });
+});
diff --git a/web/components/ui/button-group/index.stories.tsx b/web/components/ui/button-group/index.stories.tsx
new file mode 100644
index 0000000..6b2a3f6
--- /dev/null
+++ b/web/components/ui/button-group/index.stories.tsx
@@ -0,0 +1,75 @@
+import type { Meta, StoryObj } from "@storybook/react-vite";
+import { CalendarDaysIcon, CheckIcon, SettingsIcon } from "lucide-react";
+
+import { Button } from "@/components/ui/button";
+import {
+ ButtonGroup,
+ ButtonGroupSeparator,
+ ButtonGroupText,
+} from "@/components/ui/button-group";
+
+const meta = {
+ title: "UI/Button Group",
+ component: ButtonGroup,
+ tags: ["autodocs"],
+ parameters: {
+ layout: "centered",
+ },
+ argTypes: {
+ orientation: {
+ control: "select",
+ options: ["horizontal", "vertical"],
+ },
+ },
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+export const Toolbar: Story = {
+ args: {
+ orientation: "horizontal",
+ },
+ render: args => (
+
+
+
+ Sprint 12
+
+ Preview
+ Publish
+
+ ),
+};
+
+export const WithSeparators: Story = {
+ render: () => (
+
+ List
+
+ Board
+
+ Calendar
+
+ ),
+};
+
+export const Vertical: Story = {
+ render: () => (
+
+
+
+ Deployment
+
+
+
+
+
+ Promote to staging
+
+
+ Roll back to previous
+
+
+ ),
+};
diff --git a/web/components/ui/button-group/index.tsx b/web/components/ui/button-group/index.tsx
new file mode 100644
index 0000000..47b7583
--- /dev/null
+++ b/web/components/ui/button-group/index.tsx
@@ -0,0 +1,83 @@
+import { Slot } from "@radix-ui/react-slot";
+import { cva, type VariantProps } from "class-variance-authority";
+
+import { cn } from "@/lib/utils";
+import { Separator } from "@/components/ui/separator";
+
+const buttonGroupVariants = cva(
+ "flex w-fit items-stretch [&>*]:focus-visible:z-10 [&>*]:focus-visible:relative [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1 has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-md has-[>[data-slot=button-group]]:gap-2",
+ {
+ variants: {
+ orientation: {
+ horizontal:
+ "[&>*:not(:first-child)]:rounded-l-none [&>*:not(:first-child)]:border-l-0 [&>*:not(:last-child)]:rounded-r-none",
+ vertical:
+ "flex-col [&>*:not(:first-child)]:rounded-t-none [&>*:not(:first-child)]:border-t-0 [&>*:not(:last-child)]:rounded-b-none",
+ },
+ },
+ defaultVariants: {
+ orientation: "horizontal",
+ },
+ }
+);
+
+function ButtonGroup({
+ className,
+ orientation,
+ ...props
+}: React.ComponentProps<"div"> & VariantProps) {
+ return (
+
+ );
+}
+
+function ButtonGroupText({
+ className,
+ asChild = false,
+ ...props
+}: React.ComponentProps<"div"> & {
+ asChild?: boolean
+}) {
+ const Comp = asChild ? Slot : "div";
+
+ return (
+
+ );
+}
+
+function ButtonGroupSeparator({
+ className,
+ orientation = "vertical",
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+export {
+ ButtonGroup,
+ ButtonGroupSeparator,
+ ButtonGroupText,
+ buttonGroupVariants,
+};
diff --git a/web/components/ui/carousel/index.spec.tsx b/web/components/ui/carousel/index.spec.tsx
new file mode 100644
index 0000000..197459e
--- /dev/null
+++ b/web/components/ui/carousel/index.spec.tsx
@@ -0,0 +1,116 @@
+import { act, fireEvent, render, screen } from "@testing-library/react";
+
+import "@testing-library/jest-dom";
+import useEmblaCarousel from "embla-carousel-react";
+
+import {
+ Carousel,
+ CarouselContent,
+ CarouselItem,
+ CarouselNext,
+ CarouselPrevious,
+} from "./index";
+
+jest.mock("embla-carousel-react", () => ({
+ __esModule: true,
+ default: jest.fn(),
+}));
+
+const mockCarouselRef = jest.fn();
+const listeners: Record void> = {};
+const mockApi = {
+ canScrollPrev: jest.fn(),
+ canScrollNext: jest.fn(),
+ scrollPrev: jest.fn(),
+ scrollNext: jest.fn(),
+ on: jest.fn(),
+ off: jest.fn(),
+};
+const useEmblaCarouselMock = useEmblaCarousel as unknown as jest.Mock;
+
+beforeEach(() => {
+ jest.clearAllMocks();
+ Object.keys(listeners).forEach(key => delete listeners[key]);
+
+ mockApi.canScrollPrev.mockReturnValue(false);
+ mockApi.canScrollNext.mockReturnValue(true);
+ mockApi.on.mockImplementation(
+ (event: string, handler: (api: unknown) => void) => {
+ listeners[event] = handler;
+ }
+ );
+
+ useEmblaCarouselMock.mockReturnValue([mockCarouselRef, mockApi]);
+});
+
+describe("Carousel", () => {
+ test("sets navigation state from Embla API and exposes it to controls", () => {
+ const setApi = jest.fn();
+
+ render(
+
+
+ Slide 1
+ Slide 2
+
+
+
+
+ );
+
+ expect(setApi).toHaveBeenCalledWith(mockApi);
+
+ const prev = screen.getByRole("button", { name: /previous slide/i });
+ const next = screen.getByRole("button", { name: /next slide/i });
+
+ expect(prev).toBeDisabled();
+ expect(next).not.toBeDisabled();
+
+ mockApi.canScrollPrev.mockReturnValue(true);
+ mockApi.canScrollNext.mockReturnValue(false);
+
+ act(() => {
+ listeners.select?.(mockApi);
+ });
+
+ expect(prev).not.toBeDisabled();
+ expect(next).toBeDisabled();
+ });
+
+ test("responds to keyboard navigation via arrow keys", () => {
+ render(
+
+
+ Slide
+
+
+
+
+ );
+
+ const region = document.querySelector('[data-slot="carousel"]');
+
+ fireEvent.keyDown(region as HTMLElement, { key: "ArrowLeft" });
+ fireEvent.keyDown(region as HTMLElement, { key: "ArrowRight" });
+
+ expect(mockApi.scrollPrev).toHaveBeenCalled();
+ expect(mockApi.scrollNext).toHaveBeenCalled();
+ });
+
+ test("applies vertical orientation spacing to content and items", () => {
+ render(
+
+
+ Card
+
+
+ );
+
+ const content = screen.getByTestId("content");
+ const item = screen.getByTestId("item");
+
+ expect(content.className).toContain("flex-col");
+ expect(content.className).toContain("-mt-4");
+ expect(item.className).toContain("pt-4");
+ });
+});
diff --git a/web/components/ui/carousel/index.stories.tsx b/web/components/ui/carousel/index.stories.tsx
new file mode 100644
index 0000000..89765ad
--- /dev/null
+++ b/web/components/ui/carousel/index.stories.tsx
@@ -0,0 +1,91 @@
+import type { Meta, StoryObj } from "@storybook/react-vite";
+
+import {
+ Carousel,
+ CarouselContent,
+ CarouselItem,
+ CarouselNext,
+ CarouselPrevious,
+} from "@/components/ui/carousel";
+
+const meta = {
+ title: "UI/Carousel",
+ component: Carousel,
+ tags: ["autodocs"],
+ parameters: {
+ layout: "centered",
+ },
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+const slides = [
+ {
+ title: "Analytics",
+ description: "Track product usage and retention in real time.",
+ },
+ {
+ title: "Automation",
+ description: "Automate onboarding flows for new customers.",
+ },
+ {
+ title: "Security",
+ description: "Enforce MFA and device trust policies with ease.",
+ },
+ {
+ title: "Reliability",
+ description: "Multi-region redundancy keeps uptime above 99.9%.",
+ },
+ {
+ title: "Support",
+ description: "Reach the team 24/7 with guaranteed response times.",
+ },
+];
+
+export const Default: Story = {
+ render: () => (
+
+
+
+ {slides.map(item => (
+
+
+
Feature
+
{item.title}
+
+ {item.description}
+
+
+
+ ))}
+
+
+
+
+
+ ),
+};
+
+export const Vertical: Story = {
+ render: () => (
+
+
+
+ {["Overview", "Incidents", "Deployments", "SLOs"].map(item => (
+
+
+
{item}
+
+ Scroll vertically to browse recent updates.
+
+
+
+ ))}
+
+
+
+
+
+ ),
+};
diff --git a/web/components/ui/carousel/index.tsx b/web/components/ui/carousel/index.tsx
new file mode 100644
index 0000000..00b5a4e
--- /dev/null
+++ b/web/components/ui/carousel/index.tsx
@@ -0,0 +1,241 @@
+"use client";
+
+import * as React from "react";
+import useEmblaCarousel, {
+ type UseEmblaCarouselType,
+} from "embla-carousel-react";
+import { ArrowLeft, ArrowRight } from "lucide-react";
+
+import { cn } from "@/lib/utils";
+import { Button } from "@/components/ui/button";
+
+type CarouselApi = UseEmblaCarouselType[1];
+type UseCarouselParameters = Parameters;
+type CarouselOptions = UseCarouselParameters[0];
+type CarouselPlugin = UseCarouselParameters[1];
+
+type CarouselProps = {
+ opts?: CarouselOptions
+ plugins?: CarouselPlugin
+ orientation?: "horizontal" | "vertical"
+ setApi?: (api: CarouselApi) => void
+};
+
+type CarouselContextProps = {
+ carouselRef: ReturnType[0]
+ api: ReturnType[1]
+ scrollPrev: () => void
+ scrollNext: () => void
+ canScrollPrev: boolean
+ canScrollNext: boolean
+} & CarouselProps;
+
+const CarouselContext = React.createContext(null);
+
+function useCarousel() {
+ const context = React.useContext(CarouselContext);
+
+ if (!context) {
+ throw new Error("useCarousel must be used within a ");
+ }
+
+ return context;
+}
+
+function Carousel({
+ orientation = "horizontal",
+ opts,
+ setApi,
+ plugins,
+ className,
+ children,
+ ...props
+}: React.ComponentProps<"div"> & CarouselProps) {
+ const [carouselRef, api] = useEmblaCarousel(
+ {
+ ...opts,
+ axis: orientation === "horizontal" ? "x" : "y",
+ },
+ plugins
+ );
+ const [canScrollPrev, setCanScrollPrev] = React.useState(false);
+ const [canScrollNext, setCanScrollNext] = React.useState(false);
+
+ const onSelect = React.useCallback((api: CarouselApi) => {
+ if (!api) return;
+ setCanScrollPrev(api.canScrollPrev());
+ setCanScrollNext(api.canScrollNext());
+ }, []);
+
+ const scrollPrev = React.useCallback(() => {
+ api?.scrollPrev();
+ }, [api]);
+
+ const scrollNext = React.useCallback(() => {
+ api?.scrollNext();
+ }, [api]);
+
+ const handleKeyDown = React.useCallback(
+ (event: React.KeyboardEvent) => {
+ if (event.key === "ArrowLeft") {
+ event.preventDefault();
+ scrollPrev();
+ } else if (event.key === "ArrowRight") {
+ event.preventDefault();
+ scrollNext();
+ }
+ },
+ [scrollPrev, scrollNext]
+ );
+
+ React.useEffect(() => {
+ if (!api || !setApi) return;
+ setApi(api);
+ }, [api, setApi]);
+
+ React.useEffect(() => {
+ if (!api) return;
+ onSelect(api);
+ api.on("reInit", onSelect);
+ api.on("select", onSelect);
+
+ return () => {
+ api?.off("select", onSelect);
+ };
+ }, [api, onSelect]);
+
+ return (
+
+
+ {children}
+
+
+ );
+}
+
+function CarouselContent({ className, ...props }: React.ComponentProps<"div">) {
+ const { carouselRef, orientation } = useCarousel();
+
+ return (
+
+ );
+}
+
+function CarouselItem({ className, ...props }: React.ComponentProps<"div">) {
+ const { orientation } = useCarousel();
+
+ return (
+
+ );
+}
+
+function CarouselPrevious({
+ className,
+ variant = "outline",
+ size = "icon",
+ ...props
+}: React.ComponentProps) {
+ const { orientation, scrollPrev, canScrollPrev } = useCarousel();
+
+ return (
+
+
+ Previous slide
+
+ );
+}
+
+function CarouselNext({
+ className,
+ variant = "outline",
+ size = "icon",
+ ...props
+}: React.ComponentProps) {
+ const { orientation, scrollNext, canScrollNext } = useCarousel();
+
+ return (
+
+
+ Next slide
+
+ );
+}
+
+export {
+ type CarouselApi,
+ Carousel,
+ CarouselContent,
+ CarouselItem,
+ CarouselPrevious,
+ CarouselNext,
+};
diff --git a/web/components/ui/collapsible/index.spec.tsx b/web/components/ui/collapsible/index.spec.tsx
new file mode 100644
index 0000000..249db80
--- /dev/null
+++ b/web/components/ui/collapsible/index.spec.tsx
@@ -0,0 +1,50 @@
+import { fireEvent, render, screen } from "@testing-library/react";
+
+import "@testing-library/jest-dom";
+
+import {
+ Collapsible,
+ CollapsibleContent,
+ CollapsibleTrigger,
+} from "./index";
+
+describe("Collapsible", () => {
+ test("toggles open state when the trigger is clicked", () => {
+ const handleOpenChange = jest.fn();
+
+ render(
+
+ Toggle
+ Hidden content
+
+ );
+
+ const trigger = screen.getByRole("button", { name: /toggle/i });
+
+ fireEvent.click(trigger);
+ expect(handleOpenChange).toHaveBeenCalledWith(true);
+
+ fireEvent.click(trigger);
+ expect(handleOpenChange).toHaveBeenCalledWith(false);
+ });
+
+ test("exposes slot attributes for trigger and content", () => {
+ render(
+
+ Trigger
+
+ Panel content
+
+
+ );
+
+ expect(screen.getByTestId("trigger")).toHaveAttribute(
+ "data-slot",
+ "collapsible-trigger"
+ );
+
+ const content = screen.getByTestId("content");
+ expect(content).toHaveAttribute("data-slot", "collapsible-content");
+ expect(content.getAttribute("data-state")).toBe("open");
+ });
+});
diff --git a/web/components/ui/collapsible/index.stories.tsx b/web/components/ui/collapsible/index.stories.tsx
new file mode 100644
index 0000000..ac235a9
--- /dev/null
+++ b/web/components/ui/collapsible/index.stories.tsx
@@ -0,0 +1,82 @@
+import { useState } from "react";
+import type { Meta, StoryObj } from "@storybook/react-vite";
+import { ChevronDownIcon, FileTextIcon } from "lucide-react";
+
+import { Button } from "@/components/ui/button";
+import {
+ Collapsible,
+ CollapsibleContent,
+ CollapsibleTrigger,
+} from "@/components/ui/collapsible";
+
+const meta = {
+ title: "UI/Collapsible",
+ component: Collapsible,
+ tags: ["autodocs"],
+ parameters: {
+ layout: "centered",
+ },
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+export const Default: Story = {
+ render: () => {
+ const [open, setOpen] = useState(false);
+
+ return (
+
+
+ Project summary
+
+
+
+ Keep non-critical content hidden by default to keep pages scannable.
+ Use collapsibles for FAQs, advanced options, or audit details.
+
+
+ );
+ },
+};
+
+export const WithActions: Story = {
+ render: () => (
+
+
+
+
+
+ Release notes
+
+
+
+
+
+
+
+
v2.8.0
+
+ Performance improvements and bug fixes.
+
+
+
+ View diff
+
+
+
+ Reduced cold-start times for the API.
+ Added audit logs for permission updates.
+ Improved error messages on failed imports.
+
+
+
+ ),
+};
diff --git a/web/components/ui/collapsible/index.tsx b/web/components/ui/collapsible/index.tsx
new file mode 100644
index 0000000..90935c6
--- /dev/null
+++ b/web/components/ui/collapsible/index.tsx
@@ -0,0 +1,33 @@
+"use client";
+
+import * as CollapsiblePrimitive from "@radix-ui/react-collapsible";
+
+function Collapsible({
+ ...props
+}: React.ComponentProps) {
+ return ;
+}
+
+function CollapsibleTrigger({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function CollapsibleContent({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+export { Collapsible, CollapsibleTrigger, CollapsibleContent };
diff --git a/web/components/ui/hover-card/index.spec.tsx b/web/components/ui/hover-card/index.spec.tsx
new file mode 100644
index 0000000..e19d331
--- /dev/null
+++ b/web/components/ui/hover-card/index.spec.tsx
@@ -0,0 +1,46 @@
+import { render, screen } from "@testing-library/react";
+
+import "@testing-library/jest-dom";
+
+import { HoverCard, HoverCardContent, HoverCardTrigger } from "./index";
+
+describe("HoverCard", () => {
+ test("renders trigger and content with slot attributes", () => {
+ render(
+
+ Trigger
+
+ Card details
+
+
+ );
+
+ expect(screen.getByTestId("trigger")).toHaveAttribute(
+ "data-slot",
+ "hover-card-trigger"
+ );
+
+ const content = screen.getByTestId("content");
+ expect(content).toHaveAttribute("data-slot", "hover-card-content");
+ expect(content.className).toContain("rounded-md");
+ });
+
+ test("supports alignment and custom styling", () => {
+ render(
+
+ Trigger
+
+ Insights
+
+
+ );
+
+ const content = screen.getByText("Insights");
+ expect(content).toHaveClass("custom-card");
+ expect(content.className).toContain("data-[state=open]:animate-in");
+ });
+});
diff --git a/web/components/ui/hover-card/index.stories.tsx b/web/components/ui/hover-card/index.stories.tsx
new file mode 100644
index 0000000..febb040
--- /dev/null
+++ b/web/components/ui/hover-card/index.stories.tsx
@@ -0,0 +1,82 @@
+import type { Meta, StoryObj } from "@storybook/react-vite";
+import { ArrowUpRightIcon, CheckIcon } from "lucide-react";
+
+import { Button } from "@/components/ui/button";
+import {
+ HoverCard,
+ HoverCardContent,
+ HoverCardTrigger,
+} from "@/components/ui/hover-card";
+
+const meta = {
+ title: "UI/Hover Card",
+ component: HoverCard,
+ tags: ["autodocs"],
+ parameters: {
+ layout: "centered",
+ },
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+export const ProfilePreview: Story = {
+ render: () => (
+
+
+
+ @design-lead
+
+
+
+
+
+
+ Taylor Kim
+
+
+ Design Lead · San Francisco
+
+
+
+
+ Shipping a new design system for the workspace experience. Open for
+ feedback on tokens and motion guidelines.
+
+
+
+ Available for pairing this week
+
+
+
+ ),
+};
+
+export const WithCTA: Story = {
+ render: () => (
+
+
+
+ View roadmap
+
+
+
+
+
Q2 Highlights
+
+ Major bets and milestones planned for the quarter.
+
+
+
+ Multi-tenant billing rollout
+ Improved changelog and release feeds
+ Faster onboarding with saved templates
+
+
+ Explore roadmap
+
+
+
+
+ ),
+};
diff --git a/web/components/ui/hover-card/index.tsx b/web/components/ui/hover-card/index.tsx
new file mode 100644
index 0000000..fa38941
--- /dev/null
+++ b/web/components/ui/hover-card/index.tsx
@@ -0,0 +1,44 @@
+"use client";
+
+import * as React from "react";
+import * as HoverCardPrimitive from "@radix-ui/react-hover-card";
+
+import { cn } from "@/lib/utils";
+
+function HoverCard({
+ ...props
+}: React.ComponentProps) {
+ return ;
+}
+
+function HoverCardTrigger({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function HoverCardContent({
+ className,
+ align = "center",
+ sideOffset = 4,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+ );
+}
+
+export { HoverCard, HoverCardTrigger, HoverCardContent };
diff --git a/web/components/ui/input-group/index.spec.tsx b/web/components/ui/input-group/index.spec.tsx
new file mode 100644
index 0000000..f4e8860
--- /dev/null
+++ b/web/components/ui/input-group/index.spec.tsx
@@ -0,0 +1,80 @@
+import { fireEvent, render, screen } from "@testing-library/react";
+
+import "@testing-library/jest-dom";
+
+import {
+ InputGroup,
+ InputGroupAddon,
+ InputGroupButton,
+ InputGroupInput,
+ InputGroupTextarea,
+ InputGroupText,
+} from "./index";
+
+describe("InputGroup", () => {
+ test("focuses the input when clicking on an addon", () => {
+ render(
+
+
+ +1
+
+
+
+ );
+
+ const input = screen.getByPlaceholderText("Phone");
+ expect(document.activeElement).not.toBe(input);
+
+ fireEvent.click(screen.getByTestId("addon"));
+ expect(document.activeElement).toBe(input);
+ });
+
+ test("does not steal focus when clicking an interactive child", () => {
+ render(
+
+
+
+ ↵
+
+
+
+
+ );
+
+ const innerButton = screen.getByTestId("inner-button");
+ fireEvent.click(innerButton);
+
+ expect(screen.getByPlaceholderText("Search")).not.toHaveFocus();
+ });
+
+ test("supports textarea controls and block alignment", () => {
+ render(
+
+
+ Label
+
+
+
+ );
+
+ const addon = screen.getByTestId("block-addon");
+ expect(addon).toHaveAttribute("data-align", "block-start");
+ expect(addon.className).toContain("w-full");
+
+ const textarea = screen.getByLabelText("Description");
+ expect(textarea).toHaveAttribute("data-slot", "input-group-control");
+ expect(textarea.className).toContain("resize-none");
+ });
+
+ test("applies size token to embedded buttons", () => {
+ render(
+
+ Run
+
+ );
+
+ const button = screen.getByTestId("group-button");
+ expect(button).toHaveAttribute("data-size", "icon-xs");
+ expect(button.className).toContain("rounded-");
+ });
+});
diff --git a/web/components/ui/input-group/index.stories.tsx b/web/components/ui/input-group/index.stories.tsx
new file mode 100644
index 0000000..53ba591
--- /dev/null
+++ b/web/components/ui/input-group/index.stories.tsx
@@ -0,0 +1,87 @@
+import type { Meta, StoryObj } from "@storybook/react-vite";
+import { AtSignIcon, LinkIcon, SearchIcon } from "lucide-react";
+
+import {
+ InputGroup,
+ InputGroupAddon,
+ InputGroupButton,
+ InputGroupInput,
+ InputGroupText,
+ InputGroupTextarea,
+} from "@/components/ui/input-group";
+
+const meta = {
+ title: "UI/Input Group",
+ component: InputGroup,
+ tags: ["autodocs"],
+ parameters: {
+ layout: "centered",
+ },
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+export const InlineAddons: Story = {
+ render: () => (
+
+
+
+
+
+
+
+
+
+ .company.com
+
+
+
+
+ https://
+
+
+
+
+
+
+
+
+
+ ),
+};
+
+export const WithActions: Story = {
+ render: () => (
+
+
+
+
+
+
+
+
+
+ Filter
+
+
+
+ ),
+};
+
+export const WithTextarea: Story = {
+ render: () => (
+
+
+ Notes
+
+
+
+ Max 500 characters
+
+
+ ),
+};
diff --git a/web/components/ui/input-group/index.tsx b/web/components/ui/input-group/index.tsx
new file mode 100644
index 0000000..e8ac0c6
--- /dev/null
+++ b/web/components/ui/input-group/index.tsx
@@ -0,0 +1,170 @@
+"use client";
+
+import * as React from "react";
+import { cva, type VariantProps } from "class-variance-authority";
+
+import { cn } from "@/lib/utils";
+import { Button } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
+import { Textarea } from "@/components/ui/textarea";
+
+function InputGroup({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+ textarea]:h-auto",
+
+ // Variants based on alignment.
+ "has-[>[data-align=inline-start]]:[&>input]:pl-2",
+ "has-[>[data-align=inline-end]]:[&>input]:pr-2",
+ "has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>[data-align=block-start]]:[&>input]:pb-3",
+ "has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-end]]:[&>input]:pt-3",
+
+ // Focus state.
+ "has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50 has-[[data-slot=input-group-control]:focus-visible]:ring-[3px]",
+
+ // Error state.
+ "has-[[data-slot][aria-invalid=true]]:ring-destructive/20 has-[[data-slot][aria-invalid=true]]:border-destructive dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40",
+
+ className
+ )}
+ {...props}
+ />
+ );
+}
+
+const inputGroupAddonVariants = cva(
+ "text-muted-foreground flex h-auto cursor-text items-center justify-center gap-2 py-1.5 text-sm font-medium select-none [&>svg:not([class*='size-'])]:size-4 [&>kbd]:rounded-[calc(var(--radius)-5px)] group-data-[disabled=true]/input-group:opacity-50",
+ {
+ variants: {
+ align: {
+ "inline-start":
+ "order-first pl-3 has-[>button]:ml-[-0.45rem] has-[>kbd]:ml-[-0.35rem]",
+ "inline-end":
+ "order-last pr-3 has-[>button]:mr-[-0.45rem] has-[>kbd]:mr-[-0.35rem]",
+ "block-start":
+ "order-first w-full justify-start px-3 pt-3 [.border-b]:pb-3 group-has-[>input]/input-group:pt-2.5",
+ "block-end":
+ "order-last w-full justify-start px-3 pb-3 [.border-t]:pt-3 group-has-[>input]/input-group:pb-2.5",
+ },
+ },
+ defaultVariants: {
+ align: "inline-start",
+ },
+ }
+);
+
+function InputGroupAddon({
+ className,
+ align = "inline-start",
+ ...props
+}: React.ComponentProps<"div"> & VariantProps
) {
+ return (
+ {
+ if ((e.target as HTMLElement).closest("button")) {
+ return;
+ }
+ e.currentTarget.parentElement?.querySelector("input")?.focus();
+ }}
+ {...props}
+ />
+ );
+}
+
+const inputGroupButtonVariants = cva(
+ "text-sm shadow-none flex gap-2 items-center",
+ {
+ variants: {
+ size: {
+ xs: "h-6 gap-1 px-2 rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-3.5 has-[>svg]:px-2",
+ sm: "h-8 px-2.5 gap-1.5 rounded-md has-[>svg]:px-2.5",
+ "icon-xs":
+ "size-6 rounded-[calc(var(--radius)-5px)] p-0 has-[>svg]:p-0",
+ "icon-sm": "size-8 p-0 has-[>svg]:p-0",
+ },
+ },
+ defaultVariants: {
+ size: "xs",
+ },
+ }
+);
+
+function InputGroupButton({
+ className,
+ type = "button",
+ variant = "ghost",
+ size = "xs",
+ ...props
+}: Omit
, "size"> &
+ VariantProps) {
+ return (
+
+ );
+}
+
+function InputGroupText({ className, ...props }: React.ComponentProps<"span">) {
+ return (
+
+ );
+}
+
+function InputGroupInput({
+ className,
+ ...props
+}: React.ComponentProps<"input">) {
+ return (
+
+ );
+}
+
+function InputGroupTextarea({
+ className,
+ ...props
+}: React.ComponentProps<"textarea">) {
+ return (
+
+ );
+}
+
+export {
+ InputGroup,
+ InputGroupAddon,
+ InputGroupButton,
+ InputGroupText,
+ InputGroupInput,
+ InputGroupTextarea,
+};
diff --git a/web/components/ui/progress/index.spec.tsx b/web/components/ui/progress/index.spec.tsx
new file mode 100644
index 0000000..cd97600
--- /dev/null
+++ b/web/components/ui/progress/index.spec.tsx
@@ -0,0 +1,28 @@
+import { render, screen } from "@testing-library/react";
+
+import "@testing-library/jest-dom";
+
+import { Progress } from "./index";
+
+describe("Progress", () => {
+ test("renders progress indicator with calculated transform", () => {
+ render( );
+
+ const indicator = screen
+ .getByTestId("progress")
+ .querySelector('[data-slot="progress-indicator"]');
+
+ expect(indicator).toHaveStyle({ transform: "translateX(-55%)" });
+ expect(indicator?.className).toContain("bg-primary");
+ });
+
+ test("defaults to zero progress when value is not provided", () => {
+ render( );
+
+ const indicator = screen
+ .getByTestId("progress")
+ .querySelector('[data-slot="progress-indicator"]');
+
+ expect(indicator).toHaveStyle({ transform: "translateX(-100%)" });
+ });
+});
diff --git a/web/components/ui/progress/index.stories.tsx b/web/components/ui/progress/index.stories.tsx
new file mode 100644
index 0000000..d89dac2
--- /dev/null
+++ b/web/components/ui/progress/index.stories.tsx
@@ -0,0 +1,58 @@
+import type { Meta, StoryObj } from "@storybook/react-vite";
+
+import { Progress } from "@/components/ui/progress";
+
+const meta = {
+ title: "UI/Progress",
+ component: Progress,
+ tags: ["autodocs"],
+ parameters: {
+ layout: "centered",
+ },
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+export const Default: Story = {
+ args: {
+ value: 40,
+ },
+ render: args => (
+
+
+ Uploading
+ {args.value}%
+
+
+
+ ),
+};
+
+export const Stacked: Story = {
+ render: () => (
+
+
+
+ Deployment
+ 72%
+
+
+
+
+
+
+ Code coverage
+ 88%
+
+
+
+
+ ),
+};
diff --git a/web/components/ui/progress/index.tsx b/web/components/ui/progress/index.tsx
new file mode 100644
index 0000000..fc3b1af
--- /dev/null
+++ b/web/components/ui/progress/index.tsx
@@ -0,0 +1,31 @@
+"use client";
+
+import * as React from "react";
+import * as ProgressPrimitive from "@radix-ui/react-progress";
+
+import { cn } from "@/lib/utils";
+
+function Progress({
+ className,
+ value,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+ );
+}
+
+export { Progress };
diff --git a/web/components/ui/scroll-area/index.spec.tsx b/web/components/ui/scroll-area/index.spec.tsx
new file mode 100644
index 0000000..ca42230
--- /dev/null
+++ b/web/components/ui/scroll-area/index.spec.tsx
@@ -0,0 +1,43 @@
+import { render } from "@testing-library/react";
+
+import "@testing-library/jest-dom";
+
+import { ScrollArea, ScrollBar } from "./index";
+
+describe("ScrollArea", () => {
+ test("renders viewport and scrollbar slots", () => {
+ render(
+
+ Scrollable content
+
+ );
+
+ const root = document.querySelector('[data-slot="scroll-area"]');
+ const viewport = document.querySelector(
+ '[data-slot="scroll-area-viewport"]'
+ );
+ const scrollbar = document.querySelector(
+ '[data-slot="scroll-area-scrollbar"]'
+ );
+
+ expect(root).toBeInTheDocument();
+ expect(viewport?.className).toContain("rounded-[inherit]");
+ expect(scrollbar).toHaveAttribute("data-orientation", "vertical");
+ });
+
+ test("supports horizontal scrollbars with orientation styling", () => {
+ render(
+
+ Long content
+
+
+ );
+
+ const scrollbar = document.querySelector(
+ '[data-testid="horizontal-bar"]'
+ );
+ expect(scrollbar).toHaveAttribute("data-slot", "scroll-area-scrollbar");
+ expect(scrollbar).toHaveAttribute("data-orientation", "horizontal");
+ expect(scrollbar?.className).toContain("flex-col");
+ });
+});
diff --git a/web/components/ui/scroll-area/index.stories.tsx b/web/components/ui/scroll-area/index.stories.tsx
new file mode 100644
index 0000000..7afd41a
--- /dev/null
+++ b/web/components/ui/scroll-area/index.stories.tsx
@@ -0,0 +1,50 @@
+import type { Meta, StoryObj } from "@storybook/react-vite";
+
+import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
+
+const meta = {
+ title: "UI/Scroll Area",
+ component: ScrollArea,
+ tags: ["autodocs"],
+ parameters: {
+ layout: "centered",
+ },
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+export const Default: Story = {
+ render: () => (
+
+
+ {Array.from({ length: 20 }).map((_, index) => (
+
+ Activity item {index + 1}
+
+ ))}
+
+
+ ),
+};
+
+export const Horizontal: Story = {
+ render: () => (
+
+
+ {Array.from({ length: 10 }).map((_, index) => (
+
+
Card {index + 1}
+
+ Horizontal scrolling example.
+
+
+ ))}
+
+
+
+ ),
+};
diff --git a/web/components/ui/scroll-area/index.tsx b/web/components/ui/scroll-area/index.tsx
new file mode 100644
index 0000000..e6c144a
--- /dev/null
+++ b/web/components/ui/scroll-area/index.tsx
@@ -0,0 +1,58 @@
+"use client";
+
+import * as React from "react";
+import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
+
+import { cn } from "@/lib/utils";
+
+function ScrollArea({
+ className,
+ children,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+ {children}
+
+
+
+
+ );
+}
+
+function ScrollBar({
+ className,
+ orientation = "vertical",
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+ );
+}
+
+export { ScrollArea, ScrollBar };
diff --git a/web/components/ui/separator/index.spec.tsx b/web/components/ui/separator/index.spec.tsx
new file mode 100644
index 0000000..2e4561c
--- /dev/null
+++ b/web/components/ui/separator/index.spec.tsx
@@ -0,0 +1,39 @@
+import { render, screen } from "@testing-library/react";
+
+import "@testing-library/jest-dom";
+
+import { Separator } from "./index";
+
+describe("Separator", () => {
+ test("renders a decorative horizontal separator by default", () => {
+ render( );
+
+ const separator = screen.getByTestId("separator");
+ expect(separator).toHaveAttribute("data-slot", "separator");
+ expect(separator.className).toContain("data-[orientation=horizontal]:h-px");
+ expect(separator.className).toContain("data-[orientation=horizontal]:w-full");
+ expect(separator.getAttribute("role")).not.toBe("separator");
+ });
+
+ test("supports accessible vertical separators", () => {
+ render(
+
+ );
+
+ const separator = screen.getByTestId("vertical-separator");
+ expect(separator).toHaveAttribute("role", "separator");
+ expect(separator).toHaveAttribute("aria-orientation", "vertical");
+ expect(separator.className).toContain("data-[orientation=vertical]:w-px");
+ });
+
+ test("merges custom class names", () => {
+ render( );
+
+ const separator = screen.getByTestId("custom");
+ expect(separator).toHaveClass("custom-border");
+ });
+});
diff --git a/web/components/ui/separator/index.stories.tsx b/web/components/ui/separator/index.stories.tsx
new file mode 100644
index 0000000..9acdebb
--- /dev/null
+++ b/web/components/ui/separator/index.stories.tsx
@@ -0,0 +1,60 @@
+import type { Meta, StoryObj } from "@storybook/react-vite";
+
+import { Separator } from "@/components/ui/separator";
+
+const meta = {
+ title: "UI/Separator",
+ component: Separator,
+ tags: ["autodocs"],
+ parameters: {
+ layout: "centered",
+ },
+ argTypes: {
+ orientation: {
+ control: "select",
+ options: ["horizontal", "vertical"],
+ },
+ decorative: {
+ control: "boolean",
+ },
+ },
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+export const Horizontal: Story = {
+ render: args => (
+
+
+ Overview
+ 12 updates
+
+
+
+ Use separators to divide groups of related content such as lists or
+ metadata blocks.
+
+
+ ),
+ args: {
+ orientation: "horizontal",
+ decorative: true,
+ },
+};
+
+export const Vertical: Story = {
+ render: () => (
+
+
+
+
+
Resolved
+
Cleared by owners
+
+
+ ),
+};
diff --git a/web/components/ui/separator/index.tsx b/web/components/ui/separator/index.tsx
new file mode 100644
index 0000000..91411d0
--- /dev/null
+++ b/web/components/ui/separator/index.tsx
@@ -0,0 +1,28 @@
+"use client";
+
+import * as React from "react";
+import * as SeparatorPrimitive from "@radix-ui/react-separator";
+
+import { cn } from "@/lib/utils";
+
+function Separator({
+ className,
+ orientation = "horizontal",
+ decorative = true,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+export { Separator };
diff --git a/web/components/ui/textarea/index.spec.tsx b/web/components/ui/textarea/index.spec.tsx
new file mode 100644
index 0000000..991fe70
--- /dev/null
+++ b/web/components/ui/textarea/index.spec.tsx
@@ -0,0 +1,32 @@
+import { render, screen } from "@testing-library/react";
+
+import "@testing-library/jest-dom";
+
+import { Textarea } from "./index";
+
+describe("Textarea", () => {
+ test("renders with textarea slot and base styling", () => {
+ render();
+
+ const textarea = screen.getByTestId("textarea");
+ expect(textarea).toHaveAttribute("data-slot", "textarea");
+ expect(textarea.className).toContain("rounded-md");
+ expect(textarea.className).toContain("min-h-16");
+ });
+
+ test("applies aria-invalid attribute for error state", () => {
+ render();
+
+ const textarea = screen.getByPlaceholderText("Email");
+ expect(textarea).toHaveAttribute("aria-invalid", "true");
+ expect(textarea.className).toContain("aria-invalid:border-destructive");
+ });
+
+ test("merges consumer provided class names", () => {
+ render(
+
+ );
+
+ expect(screen.getByPlaceholderText("Notes")).toHaveClass("custom-textarea");
+ });
+});
diff --git a/web/components/ui/textarea/index.stories.tsx b/web/components/ui/textarea/index.stories.tsx
new file mode 100644
index 0000000..1dad2ce
--- /dev/null
+++ b/web/components/ui/textarea/index.stories.tsx
@@ -0,0 +1,57 @@
+import type { Meta, StoryObj } from "@storybook/react-vite";
+
+import { Textarea } from "@/components/ui/textarea";
+
+const meta = {
+ title: "UI/Textarea",
+ component: Textarea,
+ tags: ["autodocs"],
+ parameters: {
+ layout: "centered",
+ },
+ argTypes: {
+ disabled: {
+ control: "boolean",
+ },
+ placeholder: {
+ control: "text",
+ },
+ },
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+export const Default: Story = {
+ args: {
+ placeholder: "Share more details",
+ },
+ render: args => ,
+};
+
+export const WithLabelAndHelper: Story = {
+ render: () => (
+
+
+ Feedback
+
+
+
+ Share specific examples so the team can take action.
+
+
+ ),
+};
+
+export const Disabled: Story = {
+ args: {
+ disabled: true,
+ placeholder: "Textarea is disabled",
+ },
+ render: args => ,
+};
diff --git a/web/components/ui/textarea/index.tsx b/web/components/ui/textarea/index.tsx
new file mode 100644
index 0000000..da343f2
--- /dev/null
+++ b/web/components/ui/textarea/index.tsx
@@ -0,0 +1,18 @@
+import * as React from "react";
+
+import { cn } from "@/lib/utils";
+
+function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
+ return (
+
+ );
+}
+
+export { Textarea };
diff --git a/web/package.json b/web/package.json
index e5ba1cc..51148be 100644
--- a/web/package.json
+++ b/web/package.json
@@ -66,9 +66,12 @@
"@radix-ui/react-toggle": "1.1.1",
"@radix-ui/react-toggle-group": "1.1.1",
"@radix-ui/react-tooltip": "1.2.8",
+ "@radix-ui/react-use-controllable-state": "^1.2.2",
"@tanstack/react-query": "^5.90.11",
"@vercel/analytics": "1.3.1",
+ "@xyflow/react": "^12.10.0",
"ahooks": "^3.9.6",
+ "ai": "^5.0.108",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "1.0.4",
@@ -77,6 +80,8 @@
"embla-carousel-react": "8.5.1",
"input-otp": "1.4.2",
"lucide-react": "^0.454.0",
+ "motion": "^12.23.25",
+ "nanoid": "^5.1.6",
"next": "16.0.7",
"next-intl": "^4.5.8",
"next-themes": "^0.4.6",
@@ -86,8 +91,12 @@
"react-hook-form": "^7.60.0",
"react-resizable-panels": "^2.1.7",
"recharts": "2.15.4",
+ "shiki": "^3.19.0",
"sonner": "^1.7.4",
+ "streamdown": "^1.6.10",
"tailwind-merge": "^3.4.0",
+ "tokenlens": "^1.3.1",
+ "use-stick-to-bottom": "^1.1.1",
"vaul": "^1.1.2",
"zod": "3.25.76",
"zustand": "^5.0.8"
@@ -100,6 +109,7 @@
"@storybook/addon-a11y": "10.0.2",
"@storybook/addon-docs": "10.0.2",
"@storybook/react-vite": "10.0.2",
+ "@stylistic/eslint-plugin": "^2.11.0",
"@tailwindcss/postcss": "^4.1.9",
"@tanstack/react-query-devtools": "^5.91.1",
"@testing-library/jest-dom": "^6.9.1",
@@ -120,7 +130,6 @@
"eslint-plugin-react-compiler": "19.1.0-rc.2",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-storybook": "10.0.2",
- "@stylistic/eslint-plugin": "^2.11.0",
"happy-dom": "^20.0.10",
"husky": "^9.1.7",
"jest": "^30.0.5",
diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml
index 18fccca..736a051 100644
--- a/web/pnpm-lock.yaml
+++ b/web/pnpm-lock.yaml
@@ -98,15 +98,24 @@ importers:
'@radix-ui/react-tooltip':
specifier: 1.2.8
version: 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)
+ '@radix-ui/react-use-controllable-state':
+ specifier: ^1.2.2
+ version: 1.2.2(@types/react@19.2.7)(react@19.2.1)
'@tanstack/react-query':
specifier: ^5.90.11
version: 5.90.11(react@19.2.1)
'@vercel/analytics':
specifier: 1.3.1
- version: 1.3.1(next@16.0.7(@babel/core@7.28.3)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(react@19.2.1)
+ version: 1.3.1(next@16.0.7(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(react@19.2.1)
+ '@xyflow/react':
+ specifier: ^12.10.0
+ version: 12.10.0(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)
ahooks:
specifier: ^3.9.6
version: 3.9.6(react-dom@19.2.1(react@19.2.1))(react@19.2.1)
+ ai:
+ specifier: ^5.0.108
+ version: 5.0.108(zod@3.25.76)
class-variance-authority:
specifier: ^0.7.1
version: 0.7.1
@@ -131,12 +140,18 @@ importers:
lucide-react:
specifier: ^0.454.0
version: 0.454.0(react@19.2.1)
+ motion:
+ specifier: ^12.23.25
+ version: 12.23.25(react-dom@19.2.1(react@19.2.1))(react@19.2.1)
+ nanoid:
+ specifier: ^5.1.6
+ version: 5.1.6
next:
specifier: 16.0.7
- version: 16.0.7(@babel/core@7.28.3)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)
+ version: 16.0.7(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)
next-intl:
specifier: ^4.5.8
- version: 4.5.8(next@16.0.7(@babel/core@7.28.3)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(react@19.2.1)(typescript@5.9.3)
+ version: 4.5.8(next@16.0.7(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(react@19.2.1)(typescript@5.9.3)
next-themes:
specifier: ^0.4.6
version: 0.4.6(react-dom@19.2.1(react@19.2.1))(react@19.2.1)
@@ -158,12 +173,24 @@ importers:
recharts:
specifier: 2.15.4
version: 2.15.4(react-dom@19.2.1(react@19.2.1))(react@19.2.1)
+ shiki:
+ specifier: ^3.19.0
+ version: 3.19.0
sonner:
specifier: ^1.7.4
version: 1.7.4(react-dom@19.2.1(react@19.2.1))(react@19.2.1)
+ streamdown:
+ specifier: ^1.6.10
+ version: 1.6.10(@types/mdast@4.0.4)(micromark-util-types@2.0.2)(micromark@4.0.2)(react@19.2.1)
tailwind-merge:
specifier: ^3.4.0
version: 3.4.0
+ tokenlens:
+ specifier: ^1.3.1
+ version: 1.3.1
+ use-stick-to-bottom:
+ specifier: ^1.1.1
+ version: 1.1.1(react@19.2.1)
vaul:
specifier: ^1.1.2
version: 1.1.2(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)
@@ -306,6 +333,22 @@ packages:
'@adobe/css-tools@4.4.4':
resolution: {integrity: sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==}
+ '@ai-sdk/gateway@2.0.18':
+ resolution: {integrity: sha512-sDQcW+6ck2m0pTIHW6BPHD7S125WD3qNkx/B8sEzJp/hurocmJ5Cni0ybExg6sQMGo+fr/GWOwpHF1cmCdg5rQ==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ zod: ^3.25.76 || ^4.1.8
+
+ '@ai-sdk/provider-utils@3.0.18':
+ resolution: {integrity: sha512-ypv1xXMsgGcNKUP+hglKqtdDuMg68nWHucPPAhIENrbFAI+xCHiqPVN8Zllxyv1TNZwGWUghPxJXU+Mqps0YRQ==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ zod: ^3.25.76 || ^4.1.8
+
+ '@ai-sdk/provider@2.0.0':
+ resolution: {integrity: sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA==}
+ engines: {node: '>=18'}
+
'@alloc/quick-lru@5.2.0':
resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
engines: {node: '>=10'}
@@ -314,6 +357,9 @@ packages:
resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==}
engines: {node: '>=6.0.0'}
+ '@antfu/install-pkg@1.1.0':
+ resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==}
+
'@babel/code-frame@7.27.1':
resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==}
engines: {node: '>=6.9.0'}
@@ -543,6 +589,24 @@ packages:
'@bcoe/v8-coverage@0.2.3':
resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==}
+ '@braintree/sanitize-url@7.1.1':
+ resolution: {integrity: sha512-i1L7noDNxtFyL5DmZafWy1wRVhGehQmzZaz1HiN5e7iylJMSZR7ekOV7NsIqa5qBldlLrsKv4HbgFUVlQrz8Mw==}
+
+ '@chevrotain/cst-dts-gen@11.0.3':
+ resolution: {integrity: sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ==}
+
+ '@chevrotain/gast@11.0.3':
+ resolution: {integrity: sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q==}
+
+ '@chevrotain/regexp-to-ast@11.0.3':
+ resolution: {integrity: sha512-1fMHaBZxLFvWI067AVbGJav1eRY7N8DDvYCTwGBiE/ytKBgP8azTdgyrKyWZ9Mfh09eHWb5PgTSO8wi7U824RA==}
+
+ '@chevrotain/types@11.0.3':
+ resolution: {integrity: sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ==}
+
+ '@chevrotain/utils@11.0.3':
+ resolution: {integrity: sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==}
+
'@cspotcode/source-map-support@0.8.1':
resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
engines: {node: '>=12'}
@@ -833,6 +897,12 @@ packages:
resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==}
engines: {node: '>=18.18'}
+ '@iconify/types@2.0.0':
+ resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==}
+
+ '@iconify/utils@3.1.0':
+ resolution: {integrity: sha512-Zlzem1ZXhI1iHeeERabLNzBHdOa4VhQbqAcOQaMKuTuyZCpwKbC2R4Dd0Zo3g9EAc+Y4fiarO8HIHRAth7+skw==}
+
'@img/colour@1.0.0':
resolution: {integrity: sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==}
engines: {node: '>=18'}
@@ -1105,6 +1175,9 @@ packages:
'@types/react': '>=16'
react: '>=16'
+ '@mermaid-js/parser@0.6.3':
+ resolution: {integrity: sha512-lnjOhe7zyHjc+If7yT4zoedx2vo4sHaTmtkl1+or8BRTnCtDmcTpAjpzDSfCZrshM5bCoz0GyidzadJAH1xobA==}
+
'@napi-rs/wasm-runtime@0.2.12':
resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==}
@@ -1181,6 +1254,10 @@ packages:
resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==}
engines: {node: '>=12.4.0'}
+ '@opentelemetry/api@1.9.0':
+ resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==}
+ engines: {node: '>=8.0.0'}
+
'@oxlint/darwin-arm64@1.23.0':
resolution: {integrity: sha512-sbxoftgEMKmZQO7O4wHR9Rs7MfiHa2UH2x4QJDoc4LXqSCsI4lUIJbFQ05vX+zOUbt7CQMPdxEzExd4DqeKY2w==}
cpu: [arm64]
@@ -2247,6 +2324,27 @@ packages:
'@schummar/icu-type-parser@1.21.5':
resolution: {integrity: sha512-bXHSaW5jRTmke9Vd0h5P7BtWZG9Znqb8gSDxZnxaGSJnGwPLDPfS+3g0BKzeWqzgZPsIVZkM7m2tbo18cm5HBw==}
+ '@shikijs/core@3.19.0':
+ resolution: {integrity: sha512-L7SrRibU7ZoYi1/TrZsJOFAnnHyLTE1SwHG1yNWjZIVCqjOEmCSuK2ZO9thnRbJG6TOkPp+Z963JmpCNw5nzvA==}
+
+ '@shikijs/engine-javascript@3.19.0':
+ resolution: {integrity: sha512-ZfWJNm2VMhKkQIKT9qXbs76RRcT0SF/CAvEz0+RkpUDAoDaCx0uFdCGzSRiD9gSlhm6AHkjdieOBJMaO2eC1rQ==}
+
+ '@shikijs/engine-oniguruma@3.19.0':
+ resolution: {integrity: sha512-1hRxtYIJfJSZeM5ivbUXv9hcJP3PWRo5prG/V2sWwiubUKTa+7P62d2qxCW8jiVFX4pgRHhnHNp+qeR7Xl+6kg==}
+
+ '@shikijs/langs@3.19.0':
+ resolution: {integrity: sha512-dBMFzzg1QiXqCVQ5ONc0z2ebyoi5BKz+MtfByLm0o5/nbUu3Iz8uaTCa5uzGiscQKm7lVShfZHU1+OG3t5hgwg==}
+
+ '@shikijs/themes@3.19.0':
+ resolution: {integrity: sha512-H36qw+oh91Y0s6OlFfdSuQ0Ld+5CgB/VE6gNPK+Hk4VRbVG/XQgkjnt4KzfnnoO6tZPtKJKHPjwebOCfjd6F8A==}
+
+ '@shikijs/types@3.19.0':
+ resolution: {integrity: sha512-Z2hdeEQlzuntf/BZpFG8a+Fsw9UVXdML7w0o3TgSXV3yNESGon+bs9ITkQb3Ki7zxoXOOu5oJWqZ2uto06V9iQ==}
+
+ '@shikijs/vscode-textmate@10.0.2':
+ resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==}
+
'@sinclair/typebox@0.34.40':
resolution: {integrity: sha512-gwBNIP8ZAYev/ORDWW0QvxdwPXwxBtLsdsJgSc7eDIRt8ubP+rxUBzPsrwnu16fgEF8Bx4lh/+mvQvJzcTM6Kw==}
@@ -2256,6 +2354,9 @@ packages:
'@sinonjs/fake-timers@13.0.5':
resolution: {integrity: sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==}
+ '@standard-schema/spec@1.0.0':
+ resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==}
+
'@storybook/addon-a11y@10.0.2':
resolution: {integrity: sha512-iJJ+AXsi1oHOJ88lm/9Y2xoFJqkMOduORGk8VMPTE8fKO0THkBOSZboVHrPRiCADBLbkK9CIIzCoMzZd1XnakQ==}
peerDependencies:
@@ -2544,6 +2645,18 @@ packages:
peerDependencies:
'@testing-library/dom': '>=7.21.4'
+ '@tokenlens/core@1.3.0':
+ resolution: {integrity: sha512-d8YNHNC+q10bVpi95fELJwJyPVf1HfvBEI18eFQxRSZTdByXrP+f/ZtlhSzkx0Jl0aEmYVeBA5tPeeYRioLViQ==}
+
+ '@tokenlens/fetch@1.3.0':
+ resolution: {integrity: sha512-RONDRmETYly9xO8XMKblmrZjKSwCva4s5ebJwQNfNlChZoA5kplPoCgnWceHnn1J1iRjLVlrCNB43ichfmGBKQ==}
+
+ '@tokenlens/helpers@1.3.1':
+ resolution: {integrity: sha512-t6yL8N6ES8337E6eVSeH4hCKnPdWkZRFpupy9w5E66Q9IeqQ9IO7XQ6gh12JKjvWiRHuyyJ8MBP5I549Cr41EQ==}
+
+ '@tokenlens/models@1.3.0':
+ resolution: {integrity: sha512-9mx7ZGeewW4ndXAiD7AT1bbCk4OpJeortbjHHyNkgap+pMPPn1chY6R5zqe1ggXIUzZ2l8VOAKfPqOvpcrisJw==}
+
'@tsconfig/node10@1.0.11':
resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==}
@@ -2580,39 +2693,117 @@ packages:
'@types/d3-array@3.2.2':
resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==}
+ '@types/d3-axis@3.0.6':
+ resolution: {integrity: sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==}
+
+ '@types/d3-brush@3.0.6':
+ resolution: {integrity: sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==}
+
+ '@types/d3-chord@3.0.6':
+ resolution: {integrity: sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==}
+
'@types/d3-color@3.1.3':
resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==}
+ '@types/d3-contour@3.0.6':
+ resolution: {integrity: sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==}
+
+ '@types/d3-delaunay@6.0.4':
+ resolution: {integrity: sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==}
+
+ '@types/d3-dispatch@3.0.7':
+ resolution: {integrity: sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==}
+
+ '@types/d3-drag@3.0.7':
+ resolution: {integrity: sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==}
+
+ '@types/d3-dsv@3.0.7':
+ resolution: {integrity: sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==}
+
'@types/d3-ease@3.0.2':
resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==}
+ '@types/d3-fetch@3.0.7':
+ resolution: {integrity: sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==}
+
+ '@types/d3-force@3.0.10':
+ resolution: {integrity: sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==}
+
+ '@types/d3-format@3.0.4':
+ resolution: {integrity: sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==}
+
+ '@types/d3-geo@3.1.0':
+ resolution: {integrity: sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==}
+
+ '@types/d3-hierarchy@3.1.7':
+ resolution: {integrity: sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==}
+
'@types/d3-interpolate@3.0.4':
resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==}
'@types/d3-path@3.1.1':
resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==}
+ '@types/d3-polygon@3.0.2':
+ resolution: {integrity: sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==}
+
+ '@types/d3-quadtree@3.0.6':
+ resolution: {integrity: sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==}
+
+ '@types/d3-random@3.0.3':
+ resolution: {integrity: sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==}
+
+ '@types/d3-scale-chromatic@3.1.0':
+ resolution: {integrity: sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==}
+
'@types/d3-scale@4.0.9':
resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==}
+ '@types/d3-selection@3.0.11':
+ resolution: {integrity: sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==}
+
'@types/d3-shape@3.1.7':
resolution: {integrity: sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==}
+ '@types/d3-time-format@4.0.3':
+ resolution: {integrity: sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==}
+
'@types/d3-time@3.0.4':
resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==}
'@types/d3-timer@3.0.2':
resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==}
+ '@types/d3-transition@3.0.9':
+ resolution: {integrity: sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==}
+
+ '@types/d3-zoom@3.0.8':
+ resolution: {integrity: sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==}
+
+ '@types/d3@7.4.3':
+ resolution: {integrity: sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==}
+
+ '@types/debug@4.1.12':
+ resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==}
+
'@types/deep-eql@4.0.2':
resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==}
'@types/doctrine@0.0.9':
resolution: {integrity: sha512-eOIHzCUSH7SMfonMG1LsC2f8vxBFtho6NGBznK41R84YzPuvSBzrhEps33IsQiOW9+VL6NQ9DbjQJznk/S4uRA==}
+ '@types/estree-jsx@1.0.5':
+ resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==}
+
'@types/estree@1.0.8':
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
+ '@types/geojson@7946.0.16':
+ resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==}
+
+ '@types/hast@3.0.4':
+ resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==}
+
'@types/istanbul-lib-coverage@2.0.6':
resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==}
@@ -2634,9 +2825,18 @@ packages:
'@types/json5@0.0.29':
resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
+ '@types/katex@0.16.7':
+ resolution: {integrity: sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==}
+
+ '@types/mdast@4.0.4':
+ resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==}
+
'@types/mdx@2.0.13':
resolution: {integrity: sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==}
+ '@types/ms@2.1.0':
+ resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==}
+
'@types/node@20.19.24':
resolution: {integrity: sha512-FE5u0ezmi6y9OZEzlJfg37mqqf6ZDSF2V/NLjUyGrR9uTZ7Sb9F7bLNZ03S4XVUNRWGA7Ck4c1kK+YnuWjl+DA==}
@@ -2657,6 +2857,15 @@ packages:
'@types/stack-utils@2.0.3':
resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==}
+ '@types/trusted-types@2.0.7':
+ resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==}
+
+ '@types/unist@2.0.11':
+ resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==}
+
+ '@types/unist@3.0.3':
+ resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==}
+
'@types/whatwg-mimetype@3.0.2':
resolution: {integrity: sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA==}
@@ -2871,6 +3080,10 @@ packages:
react:
optional: true
+ '@vercel/oidc@3.0.5':
+ resolution: {integrity: sha512-fnYhv671l+eTTp48gB4zEsTW/YtRgRPnkI2nT7x6qw5rkI1Lq2hTmQIpHPgyThI0znLK+vX2n9XxKdXZ7BUbbw==}
+ engines: {node: '>= 20'}
+
'@vitest/expect@3.2.4':
resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==}
@@ -2894,6 +3107,15 @@ packages:
'@vitest/utils@3.2.4':
resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==}
+ '@xyflow/react@12.10.0':
+ resolution: {integrity: sha512-eOtz3whDMWrB4KWVatIBrKuxECHqip6PfA8fTpaS2RUGVpiEAe+nqDKsLqkViVWxDGreq0lWX71Xth/SPAzXiw==}
+ peerDependencies:
+ react: '>=17'
+ react-dom: '>=17'
+
+ '@xyflow/system@0.0.74':
+ resolution: {integrity: sha512-7v7B/PkiVrkdZzSbL+inGAo6tkR/WQHHG0/jhSvLQToCsfa8YubOGmBYd1s08tpKpihdHDZFwzQZeR69QSBb4Q==}
+
acorn-jsx@5.3.2:
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
peerDependencies:
@@ -2914,6 +3136,12 @@ packages:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ ai@5.0.108:
+ resolution: {integrity: sha512-Jex3Lb7V41NNpuqJHKgrwoU6BCLHdI1Pg4qb4GJH4jRIDRXUBySJErHjyN4oTCwbiYCeb/8II9EnqSRPq9EifA==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ zod: ^3.25.76 || ^4.1.8
+
ajv@6.12.6:
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
@@ -3063,6 +3291,9 @@ packages:
peerDependencies:
'@babel/core': ^7.11.0
+ bail@2.0.2:
+ resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==}
+
balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
@@ -3114,6 +3345,9 @@ packages:
caniuse-lite@1.0.30001737:
resolution: {integrity: sha512-BiloLiXtQNrY5UyF0+1nSJLXUENuhka2pzy2Fx5pGxqavdrxSCW4U6Pn/PoG3Efspi2frRbHpBV2XsrPE6EDlw==}
+ ccount@2.0.1:
+ resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==}
+
chai@5.3.3:
resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==}
engines: {node: '>=18'}
@@ -3130,10 +3364,30 @@ packages:
resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==}
engines: {node: '>=10'}
+ character-entities-html4@2.1.0:
+ resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==}
+
+ character-entities-legacy@3.0.0:
+ resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==}
+
+ character-entities@2.0.2:
+ resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==}
+
+ character-reference-invalid@2.0.1:
+ resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==}
+
check-error@2.1.1:
resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==}
engines: {node: '>= 16'}
+ chevrotain-allstar@0.3.1:
+ resolution: {integrity: sha512-b7g+y9A0v4mxCW1qUhf3BSVPg+/NvGErk/dOkrDaHA0nQIQGAtrOjlX//9OQtRlSCy+x9rfB5N8yC71lH1nvMw==}
+ peerDependencies:
+ chevrotain: ^11.0.0
+
+ chevrotain@11.0.3:
+ resolution: {integrity: sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==}
+
chownr@3.0.0:
resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==}
engines: {node: '>=18'}
@@ -3148,6 +3402,9 @@ packages:
class-variance-authority@0.7.1:
resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==}
+ classcat@5.0.5:
+ resolution: {integrity: sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==}
+
cli-cursor@5.0.0:
resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==}
engines: {node: '>=18'}
@@ -3190,6 +3447,9 @@ packages:
colorette@2.0.20:
resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==}
+ comma-separated-tokens@2.0.3:
+ resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==}
+
commander@14.0.0:
resolution: {integrity: sha512-2uM9rYjPvyq39NwLRqaiLtWHyDC1FvryJDa2ATTVims5YAS4PupsEQsDvP14FqhFr0P49CYDugi59xaxJlTXRA==}
engines: {node: '>=20'}
@@ -3201,12 +3461,25 @@ packages:
resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==}
engines: {node: '>= 10'}
+ commander@8.3.0:
+ resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==}
+ engines: {node: '>= 12'}
+
concat-map@0.0.1:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
+ confbox@0.1.8:
+ resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==}
+
convert-source-map@2.0.0:
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
+ cose-base@1.0.3:
+ resolution: {integrity: sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==}
+
+ cose-base@2.2.0:
+ resolution: {integrity: sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==}
+
create-require@1.1.1:
resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
@@ -3228,34 +3501,129 @@ packages:
csstype@3.2.3:
resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
+ cytoscape-cose-bilkent@4.1.0:
+ resolution: {integrity: sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==}
+ peerDependencies:
+ cytoscape: ^3.2.0
+
+ cytoscape-fcose@2.2.0:
+ resolution: {integrity: sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==}
+ peerDependencies:
+ cytoscape: ^3.2.0
+
+ cytoscape@3.33.1:
+ resolution: {integrity: sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==}
+ engines: {node: '>=0.10'}
+
+ d3-array@2.12.1:
+ resolution: {integrity: sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==}
+
d3-array@3.2.4:
resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==}
engines: {node: '>=12'}
+ d3-axis@3.0.0:
+ resolution: {integrity: sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==}
+ engines: {node: '>=12'}
+
+ d3-brush@3.0.0:
+ resolution: {integrity: sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==}
+ engines: {node: '>=12'}
+
+ d3-chord@3.0.1:
+ resolution: {integrity: sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==}
+ engines: {node: '>=12'}
+
d3-color@3.1.0:
resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==}
engines: {node: '>=12'}
+ d3-contour@4.0.2:
+ resolution: {integrity: sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==}
+ engines: {node: '>=12'}
+
+ d3-delaunay@6.0.4:
+ resolution: {integrity: sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==}
+ engines: {node: '>=12'}
+
+ d3-dispatch@3.0.1:
+ resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==}
+ engines: {node: '>=12'}
+
+ d3-drag@3.0.0:
+ resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==}
+ engines: {node: '>=12'}
+
+ d3-dsv@3.0.1:
+ resolution: {integrity: sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==}
+ engines: {node: '>=12'}
+ hasBin: true
+
d3-ease@3.0.1:
resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==}
engines: {node: '>=12'}
+ d3-fetch@3.0.1:
+ resolution: {integrity: sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==}
+ engines: {node: '>=12'}
+
+ d3-force@3.0.0:
+ resolution: {integrity: sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==}
+ engines: {node: '>=12'}
+
d3-format@3.1.0:
resolution: {integrity: sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==}
engines: {node: '>=12'}
+ d3-geo@3.1.1:
+ resolution: {integrity: sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==}
+ engines: {node: '>=12'}
+
+ d3-hierarchy@3.1.2:
+ resolution: {integrity: sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==}
+ engines: {node: '>=12'}
+
d3-interpolate@3.0.1:
resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==}
engines: {node: '>=12'}
+ d3-path@1.0.9:
+ resolution: {integrity: sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==}
+
d3-path@3.1.0:
resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==}
engines: {node: '>=12'}
+ d3-polygon@3.0.1:
+ resolution: {integrity: sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==}
+ engines: {node: '>=12'}
+
+ d3-quadtree@3.0.1:
+ resolution: {integrity: sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==}
+ engines: {node: '>=12'}
+
+ d3-random@3.0.1:
+ resolution: {integrity: sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==}
+ engines: {node: '>=12'}
+
+ d3-sankey@0.12.3:
+ resolution: {integrity: sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==}
+
+ d3-scale-chromatic@3.1.0:
+ resolution: {integrity: sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==}
+ engines: {node: '>=12'}
+
d3-scale@4.0.2:
resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==}
engines: {node: '>=12'}
+ d3-selection@3.0.0:
+ resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==}
+ engines: {node: '>=12'}
+
+ d3-shape@1.3.7:
+ resolution: {integrity: sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==}
+
d3-shape@3.2.0:
resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==}
engines: {node: '>=12'}
@@ -3272,6 +3640,23 @@ packages:
resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==}
engines: {node: '>=12'}
+ d3-transition@3.0.1:
+ resolution: {integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==}
+ engines: {node: '>=12'}
+ peerDependencies:
+ d3-selection: 2 - 3
+
+ d3-zoom@3.0.0:
+ resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==}
+ engines: {node: '>=12'}
+
+ d3@7.9.0:
+ resolution: {integrity: sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==}
+ engines: {node: '>=12'}
+
+ dagre-d3-es@7.0.13:
+ resolution: {integrity: sha512-efEhnxpSuwpYOKRm/L5KbqoZmNNukHa/Flty4Wp62JRvgH2ojwVgPgdYyr4twpieZnyRDdIH7PY2mopX26+j2Q==}
+
damerau-levenshtein@1.0.8:
resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==}
@@ -3331,6 +3716,9 @@ packages:
decimal.js@10.6.0:
resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==}
+ decode-named-character-reference@1.2.0:
+ resolution: {integrity: sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==}
+
dedent@1.6.0:
resolution: {integrity: sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA==}
peerDependencies:
@@ -3358,6 +3746,9 @@ packages:
resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==}
engines: {node: '>= 0.4'}
+ delaunator@5.0.1:
+ resolution: {integrity: sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==}
+
dequal@2.0.3:
resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
engines: {node: '>=6'}
@@ -3377,6 +3768,9 @@ packages:
detect-node-es@1.1.0:
resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==}
+ devlop@1.1.0:
+ resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==}
+
diff@4.0.2:
resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==}
engines: {node: '>=0.3.1'}
@@ -3398,6 +3792,9 @@ packages:
dom-helpers@5.2.1:
resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==}
+ dompurify@3.3.0:
+ resolution: {integrity: sha512-r+f6MYR1gGN1eJv0TVQbhA7if/U7P87cdPl3HN5rikqaBSBxLiCb/b9O+2eG0cxz0ghyU+mU1QkbsOwERMYlWQ==}
+
dunder-proto@1.0.1:
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
engines: {node: '>= 0.4'}
@@ -3445,6 +3842,10 @@ packages:
resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==}
engines: {node: '>=10.13.0'}
+ entities@6.0.1:
+ resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==}
+ engines: {node: '>=0.12'}
+
environment@1.1.0:
resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==}
engines: {node: '>=18'}
@@ -3506,6 +3907,10 @@ packages:
resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
engines: {node: '>=10'}
+ escape-string-regexp@5.0.0:
+ resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==}
+ engines: {node: '>=12'}
+
eslint-config-next@16.0.7:
resolution: {integrity: sha512-WubFGLFHfk2KivkdRGfx6cGSFhaQqhERRfyO8BRx+qiGPGp7WLKcPvYC4mdx1z3VhVRcrfFzczjjTrbJZOpnEQ==}
peerDependencies:
@@ -3644,6 +4049,9 @@ packages:
resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==}
engines: {node: '>=4.0'}
+ estree-util-is-identifier-name@3.0.0:
+ resolution: {integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==}
+
estree-walker@2.0.2:
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
@@ -3660,6 +4068,10 @@ packages:
eventemitter3@5.0.1:
resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==}
+ eventsource-parser@3.0.6:
+ resolution: {integrity: sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==}
+ engines: {node: '>=18.0.0'}
+
execa@5.1.1:
resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==}
engines: {node: '>=10'}
@@ -3672,6 +4084,9 @@ packages:
resolution: {integrity: sha512-P0te2pt+hHI5qLJkIR+iMvS+lYUZml8rKKsohVHAGY+uClp9XVbdyYNJOIjSRpHVp8s8YqxJCiHUkSYZGr8rtQ==}
engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
+ extend@3.0.2:
+ resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==}
+
fast-deep-equal@3.1.3:
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
@@ -3742,6 +4157,20 @@ packages:
fraction.js@4.3.7:
resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
+ framer-motion@12.23.25:
+ resolution: {integrity: sha512-gUHGl2e4VG66jOcH0JHhuJQr6ZNwrET9g31ZG0xdXzT0CznP7fHX4P8Bcvuc4MiUB90ysNnWX2ukHRIggkl6hQ==}
+ peerDependencies:
+ '@emotion/is-prop-valid': '*'
+ react: ^18.0.0 || ^19.0.0
+ react-dom: ^18.0.0 || ^19.0.0
+ peerDependenciesMeta:
+ '@emotion/is-prop-valid':
+ optional: true
+ react:
+ optional: true
+ react-dom:
+ optional: true
+
fs.realpath@1.0.0:
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
@@ -3841,6 +4270,9 @@ packages:
resolution: {integrity: sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==}
engines: {node: '>=10'}
+ hachure-fill@0.5.2:
+ resolution: {integrity: sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==}
+
happy-dom@20.0.10:
resolution: {integrity: sha512-6umCCHcjQrhP5oXhrHQQvLB0bwb1UzHAHdsXy+FjtKoYjUhmNZsQL8NivwM1vDvNEChJabVrUYxUnp/ZdYmy2g==}
engines: {node: '>=20.0.0'}
@@ -3872,6 +4304,49 @@ packages:
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
engines: {node: '>= 0.4'}
+ hast-util-from-dom@5.0.1:
+ resolution: {integrity: sha512-N+LqofjR2zuzTjCPzyDUdSshy4Ma6li7p/c3pA78uTwzFgENbgbUrm2ugwsOdcjI1muO+o6Dgzp9p8WHtn/39Q==}
+
+ hast-util-from-html-isomorphic@2.0.0:
+ resolution: {integrity: sha512-zJfpXq44yff2hmE0XmwEOzdWin5xwH+QIhMLOScpX91e/NSGPsAzNCvLQDIEPyO2TXi+lBmU6hjLIhV8MwP2kw==}
+
+ hast-util-from-html@2.0.3:
+ resolution: {integrity: sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==}
+
+ hast-util-from-parse5@8.0.3:
+ resolution: {integrity: sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==}
+
+ hast-util-is-element@3.0.0:
+ resolution: {integrity: sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==}
+
+ hast-util-parse-selector@4.0.0:
+ resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==}
+
+ hast-util-raw@9.1.0:
+ resolution: {integrity: sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==}
+
+ hast-util-to-html@9.0.5:
+ resolution: {integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==}
+
+ hast-util-to-jsx-runtime@2.3.6:
+ resolution: {integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==}
+
+ hast-util-to-parse5@8.0.1:
+ resolution: {integrity: sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA==}
+
+ hast-util-to-text@4.0.2:
+ resolution: {integrity: sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==}
+
+ hast-util-whitespace@3.0.0:
+ resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==}
+
+ hast@1.0.0:
+ resolution: {integrity: sha512-vFUqlRV5C+xqP76Wwq2SrM0kipnmpxJm7OfvVXpB35Fp+Fn4MV+ozr+JZr5qFvyR1q/U+Foim2x+3P+x9S1PLA==}
+ deprecated: Renamed to rehype
+
+ hastscript@9.0.1:
+ resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==}
+
hermes-estree@0.25.1:
resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==}
@@ -3881,6 +4356,12 @@ 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==}
+
human-signals@2.1.0:
resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==}
engines: {node: '>=10.17.0'}
@@ -3890,6 +4371,10 @@ packages:
engines: {node: '>=18'}
hasBin: true
+ iconv-lite@0.6.3:
+ resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
+ engines: {node: '>=0.10.0'}
+
ignore@5.3.2:
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
engines: {node: '>= 4'}
@@ -3922,6 +4407,9 @@ packages:
inherits@2.0.4:
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
+ inline-style-parser@0.2.7:
+ resolution: {integrity: sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==}
+
input-otp@1.4.2:
resolution: {integrity: sha512-l3jWwYNvrEa6NTCt7BECfCm48GvwuZzkoeG3gBL2w4CHeOXW3eKFmf9UNYkNfYc3mxMrthMnxjIE07MT0zLBQA==}
peerDependencies:
@@ -3932,6 +4420,9 @@ packages:
resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==}
engines: {node: '>= 0.4'}
+ internmap@1.0.1:
+ resolution: {integrity: sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==}
+
internmap@2.0.3:
resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==}
engines: {node: '>=12'}
@@ -3943,6 +4434,12 @@ packages:
intl-messageformat@10.7.17:
resolution: {integrity: sha512-0Ugaf65B2J76rb31drgNF1l6bGEDkbIiYc2Glx6jaZINHnwa5kDRGy8KXYuA+/8P4G0c9prAFhfVhQJJfzUuvQ==}
+ is-alphabetical@2.0.1:
+ resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==}
+
+ is-alphanumerical@2.0.1:
+ resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==}
+
is-array-buffer@3.0.5:
resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==}
engines: {node: '>= 0.4'}
@@ -3981,6 +4478,9 @@ packages:
resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==}
engines: {node: '>= 0.4'}
+ is-decimal@2.0.1:
+ resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==}
+
is-extglob@2.1.1:
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
engines: {node: '>=0.10.0'}
@@ -4013,6 +4513,9 @@ packages:
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
engines: {node: '>=0.10.0'}
+ is-hexadecimal@2.0.1:
+ resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==}
+
is-map@2.0.3:
resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==}
engines: {node: '>= 0.4'}
@@ -4029,6 +4532,10 @@ packages:
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
engines: {node: '>=0.12.0'}
+ is-plain-obj@4.1.0:
+ resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==}
+ engines: {node: '>=12'}
+
is-plain-object@5.0.0:
resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==}
engines: {node: '>=0.10.0'}
@@ -4267,6 +4774,9 @@ packages:
json-schema-traverse@0.4.1:
resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
+ json-schema@0.4.0:
+ resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==}
+
json-stable-stringify-without-jsonify@1.0.1:
resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
@@ -4286,9 +4796,20 @@ packages:
resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==}
engines: {node: '>=4.0'}
+ katex@0.16.25:
+ resolution: {integrity: sha512-woHRUZ/iF23GBP1dkDQMh1QBad9dmr8/PAwNA54VrSOVYgI12MAcE14TqnDdQOdzyEonGzMepYnqBMYdsoAr8Q==}
+ hasBin: true
+
keyv@4.5.4:
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
+ khroma@2.1.0:
+ resolution: {integrity: sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==}
+
+ langium@3.3.1:
+ resolution: {integrity: sha512-QJv/h939gDpvT+9SiLVlY7tZC3xB2qK57v0J04Sh9wpMb6MP1q8gB21L3WIo8T5P1MSMg3Ep14L7KkDCFG3y4w==}
+ engines: {node: '>=16.0.0'}
+
language-subtag-registry@0.3.23:
resolution: {integrity: sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==}
@@ -4296,6 +4817,12 @@ packages:
resolution: {integrity: sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==}
engines: {node: '>=0.10'}
+ layout-base@1.0.2:
+ resolution: {integrity: sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==}
+
+ layout-base@2.0.1:
+ resolution: {integrity: sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==}
+
leven@3.1.0:
resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==}
engines: {node: '>=6'}
@@ -4392,6 +4919,9 @@ packages:
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
engines: {node: '>=10'}
+ lodash-es@4.17.21:
+ resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==}
+
lodash.merge@4.6.2:
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
@@ -4402,6 +4932,9 @@ packages:
resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==}
engines: {node: '>=18'}
+ longest-streak@3.1.0:
+ resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==}
+
loose-envify@1.4.0:
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
hasBin: true
@@ -4420,6 +4953,11 @@ packages:
peerDependencies:
react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc
+ lucide-react@0.542.0:
+ resolution: {integrity: sha512-w3hD8/SQB7+lzU2r4VdFyzzOzKnUjTZIF/MQJGSSvni7Llewni4vuViRppfRAa2guOsY5k4jZyxw/i9DQHv+dw==}
+ peerDependencies:
+ react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
lz-string@1.5.0:
resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==}
hasBin: true
@@ -4437,78 +4975,281 @@ packages:
makeerror@1.0.12:
resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==}
+ markdown-table@3.0.4:
+ resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==}
+
+ marked@16.4.2:
+ resolution: {integrity: sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==}
+ engines: {node: '>= 20'}
+ hasBin: true
+
math-intrinsics@1.1.0:
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
engines: {node: '>= 0.4'}
- merge-stream@2.0.0:
- resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
-
- merge2@1.4.1:
- resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
- engines: {node: '>= 8'}
+ mdast-util-find-and-replace@3.0.2:
+ resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==}
- micromatch@4.0.8:
- resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
- engines: {node: '>=8.6'}
+ mdast-util-from-markdown@2.0.2:
+ resolution: {integrity: sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==}
- mimic-fn@2.1.0:
- resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
- engines: {node: '>=6'}
+ mdast-util-gfm-autolink-literal@2.0.1:
+ resolution: {integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==}
- mimic-function@5.0.1:
- resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==}
- engines: {node: '>=18'}
+ mdast-util-gfm-footnote@2.1.0:
+ resolution: {integrity: sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==}
- min-indent@1.0.1:
- resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==}
- engines: {node: '>=4'}
+ mdast-util-gfm-strikethrough@2.0.0:
+ resolution: {integrity: sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==}
- minimatch@3.1.2:
- resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
+ mdast-util-gfm-table@2.0.0:
+ resolution: {integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==}
- minimatch@9.0.5:
- resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
- engines: {node: '>=16 || 14 >=14.17'}
+ mdast-util-gfm-task-list-item@2.0.0:
+ resolution: {integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==}
- minimist@1.2.8:
- resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
+ mdast-util-gfm@3.1.0:
+ resolution: {integrity: sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==}
- minipass@7.1.2:
- resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==}
- engines: {node: '>=16 || 14 >=14.17'}
+ mdast-util-math@3.0.0:
+ resolution: {integrity: sha512-Tl9GBNeG/AhJnQM221bJR2HPvLOSnLE/T9cJI9tlc6zwQk2nPk/4f0cHkOdEixQPC/j8UtKDdITswvLAy1OZ1w==}
- minizlib@3.1.0:
- resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==}
- engines: {node: '>= 18'}
+ mdast-util-mdx-expression@2.0.1:
+ resolution: {integrity: sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==}
- mrmime@2.0.1:
- resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==}
- engines: {node: '>=10'}
+ mdast-util-mdx-jsx@3.2.0:
+ resolution: {integrity: sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==}
- ms@2.1.3:
- resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
+ mdast-util-mdxjs-esm@2.0.1:
+ resolution: {integrity: sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==}
- nano-spawn@1.0.2:
- resolution: {integrity: sha512-21t+ozMQDAL/UGgQVBbZ/xXvNO10++ZPuTmKRO8k9V3AClVRht49ahtDjfY8l1q6nSHOrE5ASfthzH3ol6R/hg==}
- engines: {node: '>=20.17'}
+ mdast-util-phrasing@4.1.0:
+ resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==}
- nanoid@3.3.11:
- resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
- engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
- hasBin: true
+ mdast-util-to-hast@13.2.1:
+ resolution: {integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==}
- napi-postinstall@0.3.3:
- resolution: {integrity: sha512-uTp172LLXSxuSYHv/kou+f6KW3SMppU9ivthaVTXian9sOt3XM/zHYHpRZiLgQoxeWfYUnslNWQHF1+G71xcow==}
- engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
- hasBin: true
+ mdast-util-to-markdown@2.1.2:
+ resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==}
- natural-compare@1.4.0:
- resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
+ mdast-util-to-string@4.0.0:
+ resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==}
- negotiator@1.0.0:
- resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==}
- engines: {node: '>= 0.6'}
+ merge-stream@2.0.0:
+ resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
+
+ merge2@1.4.1:
+ resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
+ engines: {node: '>= 8'}
+
+ mermaid@11.12.2:
+ resolution: {integrity: sha512-n34QPDPEKmaeCG4WDMGy0OT6PSyxKCfy2pJgShP+Qow2KLrvWjclwbc3yXfSIf4BanqWEhQEpngWwNp/XhZt6w==}
+
+ micromark-core-commonmark@2.0.3:
+ resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==}
+
+ micromark-extension-cjk-friendly-gfm-strikethrough@1.2.3:
+ resolution: {integrity: sha512-gSPnxgHDDqXYOBvQRq6lerrq9mjDhdtKn+7XETuXjxWcL62yZEfUdA28Ml1I2vDIPfAOIKLa0h2XDSGkInGHFQ==}
+ engines: {node: '>=16'}
+ peerDependencies:
+ micromark: ^4.0.0
+ micromark-util-types: ^2.0.0
+ peerDependenciesMeta:
+ micromark-util-types:
+ optional: true
+
+ micromark-extension-cjk-friendly-util@2.1.1:
+ resolution: {integrity: sha512-egs6+12JU2yutskHY55FyR48ZiEcFOJFyk9rsiyIhcJ6IvWB6ABBqVrBw8IobqJTDZ/wdSr9eoXDPb5S2nW1bg==}
+ engines: {node: '>=16'}
+ peerDependencies:
+ micromark-util-types: '*'
+ peerDependenciesMeta:
+ micromark-util-types:
+ optional: true
+
+ micromark-extension-cjk-friendly@1.2.3:
+ resolution: {integrity: sha512-gRzVLUdjXBLX6zNPSnHGDoo+ZTp5zy+MZm0g3sv+3chPXY7l9gW+DnrcHcZh/jiPR6MjPKO4AEJNp4Aw6V9z5Q==}
+ engines: {node: '>=16'}
+ peerDependencies:
+ micromark: ^4.0.0
+ micromark-util-types: ^2.0.0
+ peerDependenciesMeta:
+ micromark-util-types:
+ optional: true
+
+ micromark-extension-gfm-autolink-literal@2.1.0:
+ resolution: {integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==}
+
+ micromark-extension-gfm-footnote@2.1.0:
+ resolution: {integrity: sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==}
+
+ micromark-extension-gfm-strikethrough@2.1.0:
+ resolution: {integrity: sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==}
+
+ micromark-extension-gfm-table@2.1.1:
+ resolution: {integrity: sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==}
+
+ micromark-extension-gfm-tagfilter@2.0.0:
+ resolution: {integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==}
+
+ micromark-extension-gfm-task-list-item@2.1.0:
+ resolution: {integrity: sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==}
+
+ micromark-extension-gfm@3.0.0:
+ resolution: {integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==}
+
+ micromark-extension-math@3.1.0:
+ resolution: {integrity: sha512-lvEqd+fHjATVs+2v/8kg9i5Q0AP2k85H0WUOwpIVvUML8BapsMvh1XAogmQjOCsLpoKRCVQqEkQBB3NhVBcsOg==}
+
+ micromark-factory-destination@2.0.1:
+ resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==}
+
+ micromark-factory-label@2.0.1:
+ resolution: {integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==}
+
+ micromark-factory-space@2.0.1:
+ resolution: {integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==}
+
+ micromark-factory-title@2.0.1:
+ resolution: {integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==}
+
+ micromark-factory-whitespace@2.0.1:
+ resolution: {integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==}
+
+ micromark-util-character@2.1.1:
+ resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==}
+
+ micromark-util-chunked@2.0.1:
+ resolution: {integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==}
+
+ micromark-util-classify-character@2.0.1:
+ resolution: {integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==}
+
+ micromark-util-combine-extensions@2.0.1:
+ resolution: {integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==}
+
+ micromark-util-decode-numeric-character-reference@2.0.2:
+ resolution: {integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==}
+
+ micromark-util-decode-string@2.0.1:
+ resolution: {integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==}
+
+ micromark-util-encode@2.0.1:
+ resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==}
+
+ micromark-util-html-tag-name@2.0.1:
+ resolution: {integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==}
+
+ micromark-util-normalize-identifier@2.0.1:
+ resolution: {integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==}
+
+ micromark-util-resolve-all@2.0.1:
+ resolution: {integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==}
+
+ micromark-util-sanitize-uri@2.0.1:
+ resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==}
+
+ micromark-util-subtokenize@2.1.0:
+ resolution: {integrity: sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==}
+
+ micromark-util-symbol@2.0.1:
+ resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==}
+
+ micromark-util-types@2.0.2:
+ resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==}
+
+ micromark@4.0.2:
+ resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==}
+
+ micromatch@4.0.8:
+ resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
+ engines: {node: '>=8.6'}
+
+ mimic-fn@2.1.0:
+ resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
+ engines: {node: '>=6'}
+
+ mimic-function@5.0.1:
+ resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==}
+ engines: {node: '>=18'}
+
+ min-indent@1.0.1:
+ resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==}
+ engines: {node: '>=4'}
+
+ minimatch@3.1.2:
+ resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
+
+ minimatch@9.0.5:
+ resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
+ engines: {node: '>=16 || 14 >=14.17'}
+
+ minimist@1.2.8:
+ resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
+
+ minipass@7.1.2:
+ resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==}
+ engines: {node: '>=16 || 14 >=14.17'}
+
+ minizlib@3.1.0:
+ resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==}
+ engines: {node: '>= 18'}
+
+ mlly@1.8.0:
+ resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==}
+
+ motion-dom@12.23.23:
+ resolution: {integrity: sha512-n5yolOs0TQQBRUFImrRfs/+6X4p3Q4n1dUEqt/H58Vx7OW6RF+foWEgmTVDhIWJIMXOuNNL0apKH2S16en9eiA==}
+
+ motion-utils@12.23.6:
+ resolution: {integrity: sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ==}
+
+ motion@12.23.25:
+ resolution: {integrity: sha512-Fk5Y1kcgxYiTYOUjmwfXQAP7tP+iGqw/on1UID9WEL/6KpzxPr9jY2169OsjgZvXJdpraKXy0orkjaCVIl5fgQ==}
+ peerDependencies:
+ '@emotion/is-prop-valid': '*'
+ react: ^18.0.0 || ^19.0.0
+ react-dom: ^18.0.0 || ^19.0.0
+ peerDependenciesMeta:
+ '@emotion/is-prop-valid':
+ optional: true
+ react:
+ optional: true
+ react-dom:
+ optional: true
+
+ mrmime@2.0.1:
+ resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==}
+ engines: {node: '>=10'}
+
+ ms@2.1.3:
+ resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
+
+ nano-spawn@1.0.2:
+ resolution: {integrity: sha512-21t+ozMQDAL/UGgQVBbZ/xXvNO10++ZPuTmKRO8k9V3AClVRht49ahtDjfY8l1q6nSHOrE5ASfthzH3ol6R/hg==}
+ engines: {node: '>=20.17'}
+
+ nanoid@3.3.11:
+ resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
+ engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
+ hasBin: true
+
+ nanoid@5.1.6:
+ resolution: {integrity: sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==}
+ engines: {node: ^18 || >=20}
+ hasBin: true
+
+ napi-postinstall@0.3.3:
+ resolution: {integrity: sha512-uTp172LLXSxuSYHv/kou+f6KW3SMppU9ivthaVTXian9sOt3XM/zHYHpRZiLgQoxeWfYUnslNWQHF1+G71xcow==}
+ engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
+ hasBin: true
+
+ natural-compare@1.4.0:
+ resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
+
+ negotiator@1.0.0:
+ resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==}
+ engines: {node: '>= 0.6'}
next-intl-swc-plugin-extractor@4.5.8:
resolution: {integrity: sha512-hscCKUv+5GQ0CCNbvqZ8gaxnAGToCgDTbL++jgCq8SCk/ljtZDEeQZcMk46Nm6Ynn49Q/JKF4Npo/Sq1mpbusA==}
@@ -4611,6 +5352,12 @@ packages:
resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==}
engines: {node: '>=18'}
+ oniguruma-parser@0.12.1:
+ resolution: {integrity: sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==}
+
+ oniguruma-to-es@4.3.4:
+ resolution: {integrity: sha512-3VhUGN3w2eYxnTzHn+ikMI+fp/96KoRSVK9/kMTcFqj1NRDh2IhQCKvYxDnWePKRXY/AqH+Fuiyb7VHSzBjHfA==}
+
opener@1.5.2:
resolution: {integrity: sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==}
hasBin: true
@@ -4656,14 +5403,26 @@ packages:
package-json-from-dist@1.0.1:
resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}
+ package-manager-detector@1.6.0:
+ resolution: {integrity: sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==}
+
parent-module@1.0.1:
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
engines: {node: '>=6'}
+ parse-entities@4.0.2:
+ resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==}
+
parse-json@5.2.0:
resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==}
engines: {node: '>=8'}
+ parse5@7.3.0:
+ resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==}
+
+ path-data-parser@0.1.0:
+ resolution: {integrity: sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==}
+
path-exists@4.0.0:
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
engines: {node: '>=8'}
@@ -4683,6 +5442,9 @@ packages:
resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==}
engines: {node: '>=16 || 14 >=14.18'}
+ pathe@2.0.3:
+ resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
+
pathval@2.0.1:
resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==}
engines: {node: '>= 14.16'}
@@ -4711,9 +5473,18 @@ packages:
resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==}
engines: {node: '>=8'}
+ pkg-types@1.3.1:
+ resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==}
+
po-parser@1.0.2:
resolution: {integrity: sha512-yTIQL8PZy7V8c0psPoJUx7fayez+Mo/53MZgX9MPuPHx+Dt+sRPNuRbI+6Oqxnddhkd68x4Nlgon/zizL1Xg+w==}
+ points-on-curve@0.2.0:
+ resolution: {integrity: sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==}
+
+ points-on-path@0.2.1:
+ resolution: {integrity: sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==}
+
possible-typed-array-names@1.1.0:
resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==}
engines: {node: '>= 0.4'}
@@ -4749,6 +5520,9 @@ packages:
prop-types@15.8.1:
resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
+ property-information@7.1.0:
+ resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==}
+
punycode@2.3.1:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'}
@@ -4871,10 +5645,66 @@ packages:
resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==}
engines: {node: '>= 0.4'}
+ regex-recursion@6.0.2:
+ resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==}
+
+ regex-utilities@2.3.0:
+ resolution: {integrity: sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==}
+
+ regex@6.0.1:
+ resolution: {integrity: sha512-uorlqlzAKjKQZ5P+kTJr3eeJGSVroLKoHmquUj4zHWuR+hEyNqlXsSKlYYF5F4NI6nl7tWCs0apKJ0lmfsXAPA==}
+
regexp.prototype.flags@1.5.4:
resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==}
engines: {node: '>= 0.4'}
+ rehype-harden@1.1.6:
+ resolution: {integrity: sha512-5WyX6BFEWYmmbCF/S2gNRklfgPGTiGjviAjbseO4XlpqEilWBkvWwve6uU/JB3C0JvG/qxCZa3rBn8+ajy4i/A==}
+
+ rehype-katex@7.0.1:
+ resolution: {integrity: sha512-OiM2wrZ/wuhKkigASodFoo8wimG3H12LWQaH8qSPVJn9apWKFSH3YOCtbKpBorTVw/eI7cuT21XBbvwEswbIOA==}
+
+ rehype-raw@7.0.0:
+ resolution: {integrity: sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==}
+
+ remark-cjk-friendly-gfm-strikethrough@1.2.3:
+ resolution: {integrity: sha512-bXfMZtsaomK6ysNN/UGRIcasQAYkC10NtPmP0oOHOV8YOhA2TXmwRXCku4qOzjIFxAPfish5+XS0eIug2PzNZA==}
+ engines: {node: '>=16'}
+ peerDependencies:
+ '@types/mdast': ^4.0.0
+ unified: ^11.0.0
+ peerDependenciesMeta:
+ '@types/mdast':
+ optional: true
+
+ remark-cjk-friendly@1.2.3:
+ resolution: {integrity: sha512-UvAgxwlNk+l9Oqgl/9MWK2eWRS7zgBW/nXX9AthV7nd/3lNejF138E7Xbmk9Zs4WjTJGs721r7fAEc7tNFoH7g==}
+ engines: {node: '>=16'}
+ peerDependencies:
+ '@types/mdast': ^4.0.0
+ unified: ^11.0.0
+ peerDependenciesMeta:
+ '@types/mdast':
+ optional: true
+
+ remark-gfm@4.0.1:
+ resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==}
+
+ remark-math@6.0.0:
+ resolution: {integrity: sha512-MMqgnP74Igy+S3WwnhQ7kqGlEerTETXMvJhrUzDikVZ2/uogJCb+WHUg97hK9/jcfc0dkD73s3LN8zU49cTEtA==}
+
+ remark-parse@11.0.0:
+ resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==}
+
+ remark-rehype@11.1.2:
+ resolution: {integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==}
+
+ remark-stringify@11.0.0:
+ resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==}
+
+ remend@1.0.1:
+ resolution: {integrity: sha512-152puVH0qMoRJQFnaMG+rVDdf01Jq/CaED+MBuXExurJgdbkLp0c3TIe4R12o28Klx8uyGsjvFNG05aFG69G9w==}
+
require-directory@2.1.1:
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
engines: {node: '>=0.10.0'}
@@ -4917,14 +5747,23 @@ packages:
rfdc@1.4.1:
resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==}
+ robust-predicates@3.0.2:
+ resolution: {integrity: sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==}
+
rollup@4.52.5:
resolution: {integrity: sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==}
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
hasBin: true
+ roughjs@4.6.6:
+ resolution: {integrity: sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==}
+
run-parallel@1.2.0:
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
+ rw@1.3.3:
+ resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==}
+
safe-array-concat@1.1.3:
resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==}
engines: {node: '>=0.4'}
@@ -4937,6 +5776,9 @@ packages:
resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==}
engines: {node: '>= 0.4'}
+ safer-buffer@2.1.2:
+ resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
+
scheduler@0.27.0:
resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==}
@@ -4985,6 +5827,9 @@ packages:
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
engines: {node: '>=8'}
+ shiki@3.19.0:
+ resolution: {integrity: sha512-77VJr3OR/VUZzPiStyRhADmO2jApMM0V2b1qf0RpfWya8Zr1PeZev5AEpPGAAKWdiYUtcZGBE4F5QvJml1PvWA==}
+
side-channel-list@1.0.0:
resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==}
engines: {node: '>= 0.4'}
@@ -5044,6 +5889,9 @@ packages:
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
engines: {node: '>=0.10.0'}
+ space-separated-tokens@2.0.2:
+ resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==}
+
sprintf-js@1.0.3:
resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==}
@@ -5067,6 +5915,11 @@ packages:
prettier:
optional: true
+ streamdown@1.6.10:
+ resolution: {integrity: sha512-B4Y3Z/qiXl1Dc+LzAB5c52Cd1QGRiFjaDwP+ERoj1JtCykdRDM8X6HwQnn3YkpkSk0x3R7S/6LrGe1nQiElHQQ==}
+ peerDependencies:
+ react: ^18.0.0 || ^19.0.0
+
string-argv@0.3.2:
resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==}
engines: {node: '>=0.6.19'}
@@ -5110,6 +5963,9 @@ packages:
resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==}
engines: {node: '>= 0.4'}
+ stringify-entities@4.0.4:
+ resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==}
+
strip-ansi@6.0.1:
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
engines: {node: '>=8'}
@@ -5142,6 +5998,12 @@ packages:
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
engines: {node: '>=8'}
+ style-to-js@1.1.21:
+ resolution: {integrity: sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==}
+
+ style-to-object@1.0.14:
+ resolution: {integrity: sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==}
+
styled-jsx@5.1.6:
resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==}
engines: {node: '>= 12.0.0'}
@@ -5155,6 +6017,9 @@ packages:
babel-plugin-macros:
optional: true
+ stylis@4.3.6:
+ resolution: {integrity: sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==}
+
supports-color@7.2.0:
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
engines: {node: '>=8'}
@@ -5208,6 +6073,10 @@ packages:
tiny-invariant@1.3.3:
resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==}
+ tinyexec@1.0.2:
+ resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==}
+ engines: {node: '>=18'}
+
tinyglobby@0.2.15:
resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
engines: {node: '>=12.0.0'}
@@ -5227,10 +6096,19 @@ packages:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
engines: {node: '>=8.0'}
+ tokenlens@1.3.1:
+ resolution: {integrity: sha512-7oxmsS5PNCX3z+b+z07hL5vCzlgHKkCGrEQjQmWl5l+v5cUrtL7S1cuST4XThaL1XyjbTX8J5hfP0cjDJRkaLA==}
+
totalist@3.0.1:
resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==}
engines: {node: '>=6'}
+ trim-lines@3.0.1:
+ resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==}
+
+ trough@2.2.0:
+ resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==}
+
ts-api-utils@2.1.0:
resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==}
engines: {node: '>=18.12'}
@@ -5308,6 +6186,9 @@ packages:
engines: {node: '>=14.17'}
hasBin: true
+ ufo@1.6.1:
+ resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==}
+
unbox-primitive@1.1.0:
resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==}
engines: {node: '>= 0.4'}
@@ -5318,6 +6199,30 @@ packages:
undici-types@7.16.0:
resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==}
+ unified@11.0.5:
+ resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==}
+
+ unist-util-find-after@5.0.0:
+ resolution: {integrity: sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==}
+
+ unist-util-is@6.0.1:
+ resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==}
+
+ unist-util-position@5.0.0:
+ resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==}
+
+ unist-util-remove-position@5.0.0:
+ resolution: {integrity: sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==}
+
+ unist-util-stringify-position@4.0.0:
+ resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==}
+
+ unist-util-visit-parents@6.0.2:
+ resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==}
+
+ unist-util-visit@5.0.0:
+ resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==}
+
unplugin@2.3.10:
resolution: {integrity: sha512-6NCPkv1ClwH+/BGE9QeoTIl09nuiAt0gS28nn1PvYXsGKRwM2TCbFA2QiilmehPDTXIe684k4rZI1yl3A1PCUw==}
engines: {node: '>=18.12.0'}
@@ -5359,11 +6264,20 @@ packages:
'@types/react':
optional: true
+ use-stick-to-bottom@1.1.1:
+ resolution: {integrity: sha512-JkDp0b0tSmv7HQOOpL1hT7t7QaoUBXkq045WWWOFDTlLGRzgIIyW7vyzOIJzY7L2XVIG7j1yUxeDj2LHm9Vwng==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
use-sync-external-store@1.6.0:
resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==}
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ uuid@11.1.0:
+ resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==}
+ hasBin: true
+
v8-compile-cache-lib@3.0.1:
resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==}
@@ -5377,6 +6291,15 @@ packages:
react: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc
+ vfile-location@5.0.3:
+ resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==}
+
+ vfile-message@4.0.3:
+ resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==}
+
+ vfile@6.0.3:
+ resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==}
+
victory-vendor@36.9.2:
resolution: {integrity: sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==}
@@ -5420,9 +6343,32 @@ packages:
yaml:
optional: true
+ vscode-jsonrpc@8.2.0:
+ resolution: {integrity: sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==}
+ engines: {node: '>=14.0.0'}
+
+ vscode-languageserver-protocol@3.17.5:
+ resolution: {integrity: sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==}
+
+ vscode-languageserver-textdocument@1.0.12:
+ resolution: {integrity: sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==}
+
+ vscode-languageserver-types@3.17.5:
+ resolution: {integrity: sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==}
+
+ vscode-languageserver@9.0.1:
+ resolution: {integrity: sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==}
+ hasBin: true
+
+ vscode-uri@3.0.8:
+ resolution: {integrity: sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==}
+
walker@1.0.8:
resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==}
+ web-namespaces@2.0.1:
+ resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==}
+
webpack-bundle-analyzer@4.10.1:
resolution: {integrity: sha512-s3P7pgexgT/HTUSYgxJyn28A+99mmLq4HsJepMPzu0R8ImJc52QNqaFYW1Z2z2uIb1/J3eYgaAWVpaC+v/1aAQ==}
engines: {node: '>= 10.13.0'}
@@ -5550,6 +6496,21 @@ packages:
zod@3.25.76:
resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==}
+ zustand@4.5.7:
+ resolution: {integrity: sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==}
+ engines: {node: '>=12.7.0'}
+ peerDependencies:
+ '@types/react': '>=16.8'
+ immer: '>=9.0.6'
+ react: '>=16.8'
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ immer:
+ optional: true
+ react:
+ optional: true
+
zustand@5.0.8:
resolution: {integrity: sha512-gyPKpIaxY9XcO2vSMrLbiER7QMAMGOQZVRdJ6Zi782jkbzZygq5GI9nG8g+sMgitRtndwaBSl7uiqC49o1SSiw==}
engines: {node: '>=12.20.0'}
@@ -5568,10 +6529,31 @@ packages:
use-sync-external-store:
optional: true
+ zwitch@2.0.4:
+ resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
+
snapshots:
'@adobe/css-tools@4.4.4': {}
+ '@ai-sdk/gateway@2.0.18(zod@3.25.76)':
+ dependencies:
+ '@ai-sdk/provider': 2.0.0
+ '@ai-sdk/provider-utils': 3.0.18(zod@3.25.76)
+ '@vercel/oidc': 3.0.5
+ zod: 3.25.76
+
+ '@ai-sdk/provider-utils@3.0.18(zod@3.25.76)':
+ dependencies:
+ '@ai-sdk/provider': 2.0.0
+ '@standard-schema/spec': 1.0.0
+ eventsource-parser: 3.0.6
+ zod: 3.25.76
+
+ '@ai-sdk/provider@2.0.0':
+ dependencies:
+ json-schema: 0.4.0
+
'@alloc/quick-lru@5.2.0': {}
'@ampproject/remapping@2.3.0':
@@ -5579,6 +6561,11 @@ snapshots:
'@jridgewell/gen-mapping': 0.3.13
'@jridgewell/trace-mapping': 0.3.31
+ '@antfu/install-pkg@1.1.0':
+ dependencies:
+ package-manager-detector: 1.6.0
+ tinyexec: 1.0.2
+
'@babel/code-frame@7.27.1':
dependencies:
'@babel/helper-validator-identifier': 7.27.1
@@ -5855,6 +6842,25 @@ snapshots:
'@bcoe/v8-coverage@0.2.3': {}
+ '@braintree/sanitize-url@7.1.1': {}
+
+ '@chevrotain/cst-dts-gen@11.0.3':
+ dependencies:
+ '@chevrotain/gast': 11.0.3
+ '@chevrotain/types': 11.0.3
+ lodash-es: 4.17.21
+
+ '@chevrotain/gast@11.0.3':
+ dependencies:
+ '@chevrotain/types': 11.0.3
+ lodash-es: 4.17.21
+
+ '@chevrotain/regexp-to-ast@11.0.3': {}
+
+ '@chevrotain/types@11.0.3': {}
+
+ '@chevrotain/utils@11.0.3': {}
+
'@cspotcode/source-map-support@0.8.1':
dependencies:
'@jridgewell/trace-mapping': 0.3.9
@@ -6087,6 +7093,14 @@ snapshots:
'@humanwhocodes/retry@0.4.3': {}
+ '@iconify/types@2.0.0': {}
+
+ '@iconify/utils@3.1.0':
+ dependencies:
+ '@antfu/install-pkg': 1.1.0
+ '@iconify/types': 2.0.0
+ mlly: 1.8.0
+
'@img/colour@1.0.0':
optional: true
@@ -6431,6 +7445,10 @@ snapshots:
'@types/react': 19.2.7
react: 19.2.1
+ '@mermaid-js/parser@0.6.3':
+ dependencies:
+ langium: 3.3.1
+
'@napi-rs/wasm-runtime@0.2.12':
dependencies:
'@emnapi/core': 1.4.5
@@ -6489,6 +7507,8 @@ snapshots:
'@nolyfill/is-core-module@1.0.39': {}
+ '@opentelemetry/api@1.9.0': {}
+
'@oxlint/darwin-arm64@1.23.0':
optional: true
@@ -7501,6 +8521,39 @@ snapshots:
'@schummar/icu-type-parser@1.21.5': {}
+ '@shikijs/core@3.19.0':
+ dependencies:
+ '@shikijs/types': 3.19.0
+ '@shikijs/vscode-textmate': 10.0.2
+ '@types/hast': 3.0.4
+ hast-util-to-html: 9.0.5
+
+ '@shikijs/engine-javascript@3.19.0':
+ dependencies:
+ '@shikijs/types': 3.19.0
+ '@shikijs/vscode-textmate': 10.0.2
+ oniguruma-to-es: 4.3.4
+
+ '@shikijs/engine-oniguruma@3.19.0':
+ dependencies:
+ '@shikijs/types': 3.19.0
+ '@shikijs/vscode-textmate': 10.0.2
+
+ '@shikijs/langs@3.19.0':
+ dependencies:
+ '@shikijs/types': 3.19.0
+
+ '@shikijs/themes@3.19.0':
+ dependencies:
+ '@shikijs/types': 3.19.0
+
+ '@shikijs/types@3.19.0':
+ dependencies:
+ '@shikijs/vscode-textmate': 10.0.2
+ '@types/hast': 3.0.4
+
+ '@shikijs/vscode-textmate@10.0.2': {}
+
'@sinclair/typebox@0.34.40': {}
'@sinonjs/commons@3.0.1':
@@ -7511,6 +8564,8 @@ snapshots:
dependencies:
'@sinonjs/commons': 3.0.1
+ '@standard-schema/spec@1.0.0': {}
+
'@storybook/addon-a11y@10.0.2(storybook@10.0.2(@testing-library/dom@10.4.1)(prettier@3.6.2)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(vite@6.4.1(@types/node@24.9.1)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.1)))':
dependencies:
'@storybook/global': 5.0.0
@@ -7788,11 +8843,26 @@ snapshots:
dependencies:
'@testing-library/dom': 10.4.1
- '@tsconfig/node10@1.0.11': {}
+ '@tokenlens/core@1.3.0': {}
- '@tsconfig/node12@1.0.11': {}
+ '@tokenlens/fetch@1.3.0':
+ dependencies:
+ '@tokenlens/core': 1.3.0
- '@tsconfig/node14@1.0.3': {}
+ '@tokenlens/helpers@1.3.1':
+ dependencies:
+ '@tokenlens/core': 1.3.0
+ '@tokenlens/fetch': 1.3.0
+
+ '@tokenlens/models@1.3.0':
+ dependencies:
+ '@tokenlens/core': 1.3.0
+
+ '@tsconfig/node10@1.0.11': {}
+
+ '@tsconfig/node12@1.0.11': {}
+
+ '@tsconfig/node14@1.0.3': {}
'@tsconfig/node16@1.0.4': {}
@@ -7831,34 +8901,141 @@ snapshots:
'@types/d3-array@3.2.2': {}
+ '@types/d3-axis@3.0.6':
+ dependencies:
+ '@types/d3-selection': 3.0.11
+
+ '@types/d3-brush@3.0.6':
+ dependencies:
+ '@types/d3-selection': 3.0.11
+
+ '@types/d3-chord@3.0.6': {}
+
'@types/d3-color@3.1.3': {}
+ '@types/d3-contour@3.0.6':
+ dependencies:
+ '@types/d3-array': 3.2.2
+ '@types/geojson': 7946.0.16
+
+ '@types/d3-delaunay@6.0.4': {}
+
+ '@types/d3-dispatch@3.0.7': {}
+
+ '@types/d3-drag@3.0.7':
+ dependencies:
+ '@types/d3-selection': 3.0.11
+
+ '@types/d3-dsv@3.0.7': {}
+
'@types/d3-ease@3.0.2': {}
+ '@types/d3-fetch@3.0.7':
+ dependencies:
+ '@types/d3-dsv': 3.0.7
+
+ '@types/d3-force@3.0.10': {}
+
+ '@types/d3-format@3.0.4': {}
+
+ '@types/d3-geo@3.1.0':
+ dependencies:
+ '@types/geojson': 7946.0.16
+
+ '@types/d3-hierarchy@3.1.7': {}
+
'@types/d3-interpolate@3.0.4':
dependencies:
'@types/d3-color': 3.1.3
'@types/d3-path@3.1.1': {}
+ '@types/d3-polygon@3.0.2': {}
+
+ '@types/d3-quadtree@3.0.6': {}
+
+ '@types/d3-random@3.0.3': {}
+
+ '@types/d3-scale-chromatic@3.1.0': {}
+
'@types/d3-scale@4.0.9':
dependencies:
'@types/d3-time': 3.0.4
+ '@types/d3-selection@3.0.11': {}
+
'@types/d3-shape@3.1.7':
dependencies:
'@types/d3-path': 3.1.1
+ '@types/d3-time-format@4.0.3': {}
+
'@types/d3-time@3.0.4': {}
'@types/d3-timer@3.0.2': {}
+ '@types/d3-transition@3.0.9':
+ dependencies:
+ '@types/d3-selection': 3.0.11
+
+ '@types/d3-zoom@3.0.8':
+ dependencies:
+ '@types/d3-interpolate': 3.0.4
+ '@types/d3-selection': 3.0.11
+
+ '@types/d3@7.4.3':
+ dependencies:
+ '@types/d3-array': 3.2.2
+ '@types/d3-axis': 3.0.6
+ '@types/d3-brush': 3.0.6
+ '@types/d3-chord': 3.0.6
+ '@types/d3-color': 3.1.3
+ '@types/d3-contour': 3.0.6
+ '@types/d3-delaunay': 6.0.4
+ '@types/d3-dispatch': 3.0.7
+ '@types/d3-drag': 3.0.7
+ '@types/d3-dsv': 3.0.7
+ '@types/d3-ease': 3.0.2
+ '@types/d3-fetch': 3.0.7
+ '@types/d3-force': 3.0.10
+ '@types/d3-format': 3.0.4
+ '@types/d3-geo': 3.1.0
+ '@types/d3-hierarchy': 3.1.7
+ '@types/d3-interpolate': 3.0.4
+ '@types/d3-path': 3.1.1
+ '@types/d3-polygon': 3.0.2
+ '@types/d3-quadtree': 3.0.6
+ '@types/d3-random': 3.0.3
+ '@types/d3-scale': 4.0.9
+ '@types/d3-scale-chromatic': 3.1.0
+ '@types/d3-selection': 3.0.11
+ '@types/d3-shape': 3.1.7
+ '@types/d3-time': 3.0.4
+ '@types/d3-time-format': 4.0.3
+ '@types/d3-timer': 3.0.2
+ '@types/d3-transition': 3.0.9
+ '@types/d3-zoom': 3.0.8
+
+ '@types/debug@4.1.12':
+ dependencies:
+ '@types/ms': 2.1.0
+
'@types/deep-eql@4.0.2': {}
'@types/doctrine@0.0.9': {}
+ '@types/estree-jsx@1.0.5':
+ dependencies:
+ '@types/estree': 1.0.8
+
'@types/estree@1.0.8': {}
+ '@types/geojson@7946.0.16': {}
+
+ '@types/hast@3.0.4':
+ dependencies:
+ '@types/unist': 3.0.3
+
'@types/istanbul-lib-coverage@2.0.6': {}
'@types/istanbul-lib-report@3.0.3':
@@ -7880,8 +9057,16 @@ snapshots:
'@types/json5@0.0.29': {}
+ '@types/katex@0.16.7': {}
+
+ '@types/mdast@4.0.4':
+ dependencies:
+ '@types/unist': 3.0.3
+
'@types/mdx@2.0.13': {}
+ '@types/ms@2.1.0': {}
+
'@types/node@20.19.24':
dependencies:
undici-types: 6.21.0
@@ -7902,6 +9087,13 @@ snapshots:
'@types/stack-utils@2.0.3': {}
+ '@types/trusted-types@2.0.7':
+ optional: true
+
+ '@types/unist@2.0.11': {}
+
+ '@types/unist@3.0.3': {}
+
'@types/whatwg-mimetype@3.0.2': {}
'@types/yargs-parser@21.0.3': {}
@@ -8115,13 +9307,15 @@ snapshots:
'@unrs/resolver-binding-win32-x64-msvc@1.11.1':
optional: true
- '@vercel/analytics@1.3.1(next@16.0.7(@babel/core@7.28.3)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(react@19.2.1)':
+ '@vercel/analytics@1.3.1(next@16.0.7(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(react@19.2.1)':
dependencies:
server-only: 0.0.1
optionalDependencies:
- next: 16.0.7(@babel/core@7.28.3)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)
+ next: 16.0.7(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)
react: 19.2.1
+ '@vercel/oidc@3.0.5': {}
+
'@vitest/expect@3.2.4':
dependencies:
'@types/chai': 5.2.3
@@ -8152,6 +9346,29 @@ snapshots:
loupe: 3.2.1
tinyrainbow: 2.0.0
+ '@xyflow/react@12.10.0(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)':
+ dependencies:
+ '@xyflow/system': 0.0.74
+ classcat: 5.0.5
+ react: 19.2.1
+ react-dom: 19.2.1(react@19.2.1)
+ zustand: 4.5.7(@types/react@19.2.7)(react@19.2.1)
+ transitivePeerDependencies:
+ - '@types/react'
+ - immer
+
+ '@xyflow/system@0.0.74':
+ dependencies:
+ '@types/d3-drag': 3.0.7
+ '@types/d3-interpolate': 3.0.4
+ '@types/d3-selection': 3.0.11
+ '@types/d3-transition': 3.0.9
+ '@types/d3-zoom': 3.0.8
+ d3-drag: 3.0.0
+ d3-interpolate: 3.0.1
+ d3-selection: 3.0.0
+ d3-zoom: 3.0.0
+
acorn-jsx@5.3.2(acorn@8.15.0):
dependencies:
acorn: 8.15.0
@@ -8177,6 +9394,14 @@ snapshots:
screenfull: 5.2.0
tslib: 2.8.1
+ ai@5.0.108(zod@3.25.76):
+ dependencies:
+ '@ai-sdk/gateway': 2.0.18(zod@3.25.76)
+ '@ai-sdk/provider': 2.0.0
+ '@ai-sdk/provider-utils': 3.0.18(zod@3.25.76)
+ '@opentelemetry/api': 1.9.0
+ zod: 3.25.76
+
ajv@6.12.6:
dependencies:
fast-deep-equal: 3.1.3
@@ -8380,6 +9605,8 @@ snapshots:
babel-plugin-jest-hoist: 30.0.1
babel-preset-current-node-syntax: 1.2.0(@babel/core@7.28.3)
+ bail@2.0.2: {}
+
balanced-match@1.0.2: {}
brace-expansion@1.1.12:
@@ -8433,6 +9660,8 @@ snapshots:
caniuse-lite@1.0.30001737: {}
+ ccount@2.0.1: {}
+
chai@5.3.3:
dependencies:
assertion-error: 2.0.1
@@ -8450,8 +9679,30 @@ snapshots:
char-regex@1.0.2: {}
+ character-entities-html4@2.1.0: {}
+
+ character-entities-legacy@3.0.0: {}
+
+ character-entities@2.0.2: {}
+
+ character-reference-invalid@2.0.1: {}
+
check-error@2.1.1: {}
+ chevrotain-allstar@0.3.1(chevrotain@11.0.3):
+ dependencies:
+ chevrotain: 11.0.3
+ lodash-es: 4.17.21
+
+ chevrotain@11.0.3:
+ dependencies:
+ '@chevrotain/cst-dts-gen': 11.0.3
+ '@chevrotain/gast': 11.0.3
+ '@chevrotain/regexp-to-ast': 11.0.3
+ '@chevrotain/types': 11.0.3
+ '@chevrotain/utils': 11.0.3
+ lodash-es: 4.17.21
+
chownr@3.0.0: {}
ci-info@4.3.0: {}
@@ -8462,6 +9713,8 @@ snapshots:
dependencies:
clsx: 2.1.1
+ classcat@5.0.5: {}
+
cli-cursor@5.0.0:
dependencies:
restore-cursor: 5.1.0
@@ -8505,6 +9758,8 @@ snapshots:
colorette@2.0.20: {}
+ comma-separated-tokens@2.0.3: {}
+
commander@14.0.0: {}
commander@2.20.3:
@@ -8512,10 +9767,22 @@ snapshots:
commander@7.2.0: {}
+ commander@8.3.0: {}
+
concat-map@0.0.1: {}
+ confbox@0.1.8: {}
+
convert-source-map@2.0.0: {}
+ cose-base@1.0.3:
+ dependencies:
+ layout-base: 1.0.2
+
+ cose-base@2.2.0:
+ dependencies:
+ layout-base: 2.0.1
+
create-require@1.1.1: {}
cross-env@10.1.0:
@@ -8535,22 +9802,107 @@ snapshots:
csstype@3.2.3: {}
+ cytoscape-cose-bilkent@4.1.0(cytoscape@3.33.1):
+ dependencies:
+ cose-base: 1.0.3
+ cytoscape: 3.33.1
+
+ cytoscape-fcose@2.2.0(cytoscape@3.33.1):
+ dependencies:
+ cose-base: 2.2.0
+ cytoscape: 3.33.1
+
+ cytoscape@3.33.1: {}
+
+ d3-array@2.12.1:
+ dependencies:
+ internmap: 1.0.1
+
d3-array@3.2.4:
dependencies:
internmap: 2.0.3
+ d3-axis@3.0.0: {}
+
+ d3-brush@3.0.0:
+ dependencies:
+ d3-dispatch: 3.0.1
+ d3-drag: 3.0.0
+ d3-interpolate: 3.0.1
+ d3-selection: 3.0.0
+ d3-transition: 3.0.1(d3-selection@3.0.0)
+
+ d3-chord@3.0.1:
+ dependencies:
+ d3-path: 3.1.0
+
d3-color@3.1.0: {}
+ d3-contour@4.0.2:
+ dependencies:
+ d3-array: 3.2.4
+
+ d3-delaunay@6.0.4:
+ dependencies:
+ delaunator: 5.0.1
+
+ d3-dispatch@3.0.1: {}
+
+ d3-drag@3.0.0:
+ dependencies:
+ d3-dispatch: 3.0.1
+ d3-selection: 3.0.0
+
+ d3-dsv@3.0.1:
+ dependencies:
+ commander: 7.2.0
+ iconv-lite: 0.6.3
+ rw: 1.3.3
+
d3-ease@3.0.1: {}
+ d3-fetch@3.0.1:
+ dependencies:
+ d3-dsv: 3.0.1
+
+ d3-force@3.0.0:
+ dependencies:
+ d3-dispatch: 3.0.1
+ d3-quadtree: 3.0.1
+ d3-timer: 3.0.1
+
d3-format@3.1.0: {}
+ d3-geo@3.1.1:
+ dependencies:
+ d3-array: 3.2.4
+
+ d3-hierarchy@3.1.2: {}
+
d3-interpolate@3.0.1:
dependencies:
d3-color: 3.1.0
+ d3-path@1.0.9: {}
+
d3-path@3.1.0: {}
+ d3-polygon@3.0.1: {}
+
+ d3-quadtree@3.0.1: {}
+
+ d3-random@3.0.1: {}
+
+ d3-sankey@0.12.3:
+ dependencies:
+ d3-array: 2.12.1
+ d3-shape: 1.3.7
+
+ d3-scale-chromatic@3.1.0:
+ dependencies:
+ d3-color: 3.1.0
+ d3-interpolate: 3.0.1
+
d3-scale@4.0.2:
dependencies:
d3-array: 3.2.4
@@ -8559,6 +9911,12 @@ snapshots:
d3-time: 3.1.0
d3-time-format: 4.1.0
+ d3-selection@3.0.0: {}
+
+ d3-shape@1.3.7:
+ dependencies:
+ d3-path: 1.0.9
+
d3-shape@3.2.0:
dependencies:
d3-path: 3.1.0
@@ -8573,6 +9931,61 @@ snapshots:
d3-timer@3.0.1: {}
+ d3-transition@3.0.1(d3-selection@3.0.0):
+ dependencies:
+ d3-color: 3.1.0
+ d3-dispatch: 3.0.1
+ d3-ease: 3.0.1
+ d3-interpolate: 3.0.1
+ d3-selection: 3.0.0
+ d3-timer: 3.0.1
+
+ d3-zoom@3.0.0:
+ dependencies:
+ d3-dispatch: 3.0.1
+ d3-drag: 3.0.0
+ d3-interpolate: 3.0.1
+ d3-selection: 3.0.0
+ d3-transition: 3.0.1(d3-selection@3.0.0)
+
+ d3@7.9.0:
+ dependencies:
+ d3-array: 3.2.4
+ d3-axis: 3.0.0
+ d3-brush: 3.0.0
+ d3-chord: 3.0.1
+ d3-color: 3.1.0
+ d3-contour: 4.0.2
+ d3-delaunay: 6.0.4
+ d3-dispatch: 3.0.1
+ d3-drag: 3.0.0
+ d3-dsv: 3.0.1
+ d3-ease: 3.0.1
+ d3-fetch: 3.0.1
+ d3-force: 3.0.0
+ d3-format: 3.1.0
+ d3-geo: 3.1.1
+ d3-hierarchy: 3.1.2
+ d3-interpolate: 3.0.1
+ d3-path: 3.1.0
+ d3-polygon: 3.0.1
+ d3-quadtree: 3.0.1
+ d3-random: 3.0.1
+ d3-scale: 4.0.2
+ d3-scale-chromatic: 3.1.0
+ d3-selection: 3.0.0
+ d3-shape: 3.2.0
+ d3-time: 3.1.0
+ d3-time-format: 4.1.0
+ d3-timer: 3.0.1
+ d3-transition: 3.0.1(d3-selection@3.0.0)
+ d3-zoom: 3.0.0
+
+ dagre-d3-es@7.0.13:
+ dependencies:
+ d3: 7.9.0
+ lodash-es: 4.17.21
+
damerau-levenshtein@1.0.8: {}
data-view-buffer@1.0.2:
@@ -8617,6 +10030,10 @@ snapshots:
decimal.js@10.6.0: {}
+ decode-named-character-reference@1.2.0:
+ dependencies:
+ character-entities: 2.0.2
+
dedent@1.6.0: {}
deep-eql@5.0.2: {}
@@ -8637,6 +10054,10 @@ snapshots:
has-property-descriptors: 1.0.2
object-keys: 1.1.1
+ delaunator@5.0.1:
+ dependencies:
+ robust-predicates: 3.0.2
+
dequal@2.0.3: {}
detect-libc@2.1.0: {}
@@ -8648,6 +10069,10 @@ snapshots:
detect-node-es@1.1.0: {}
+ devlop@1.1.0:
+ dependencies:
+ dequal: 2.0.3
+
diff@4.0.2: {}
doctrine@2.1.0:
@@ -8667,6 +10092,10 @@ snapshots:
'@babel/runtime': 7.28.4
csstype: 3.1.3
+ dompurify@3.3.0:
+ optionalDependencies:
+ '@types/trusted-types': 2.0.7
+
dunder-proto@1.0.1:
dependencies:
call-bind-apply-helpers: 1.0.2
@@ -8706,6 +10135,8 @@ snapshots:
graceful-fs: 4.2.11
tapable: 2.2.3
+ entities@6.0.1: {}
+
environment@1.1.0: {}
error-ex@1.3.2:
@@ -8856,6 +10287,8 @@ snapshots:
escape-string-regexp@4.0.0: {}
+ escape-string-regexp@5.0.0: {}
+
eslint-config-next@16.0.7(@typescript-eslint/parser@8.48.1(eslint@9.36.0(jiti@2.5.1))(typescript@5.9.3))(eslint@9.36.0(jiti@2.5.1))(typescript@5.9.3):
dependencies:
'@next/eslint-plugin-next': 16.0.7
@@ -9089,6 +10522,8 @@ snapshots:
estraverse@5.3.0: {}
+ estree-util-is-identifier-name@3.0.0: {}
+
estree-walker@2.0.2: {}
estree-walker@3.0.3:
@@ -9101,6 +10536,8 @@ snapshots:
eventemitter3@5.0.1: {}
+ eventsource-parser@3.0.6: {}
+
execa@5.1.1:
dependencies:
cross-spawn: 7.0.6
@@ -9124,6 +10561,8 @@ snapshots:
jest-mock: 30.0.5
jest-util: 30.0.5
+ extend@3.0.2: {}
+
fast-deep-equal@3.1.3: {}
fast-equals@5.3.2: {}
@@ -9196,6 +10635,15 @@ snapshots:
fraction.js@4.3.7: {}
+ framer-motion@12.23.25(react-dom@19.2.1(react@19.2.1))(react@19.2.1):
+ dependencies:
+ motion-dom: 12.23.23
+ motion-utils: 12.23.6
+ tslib: 2.8.1
+ optionalDependencies:
+ react: 19.2.1
+ react-dom: 19.2.1(react@19.2.1)
+
fs.realpath@1.0.0: {}
fsevents@2.3.3:
@@ -9299,6 +10747,8 @@ snapshots:
dependencies:
duplexer: 0.1.2
+ hachure-fill@0.5.2: {}
+
happy-dom@20.0.10:
dependencies:
'@types/node': 20.19.24
@@ -9327,6 +10777,128 @@ snapshots:
dependencies:
function-bind: 1.1.2
+ hast-util-from-dom@5.0.1:
+ dependencies:
+ '@types/hast': 3.0.4
+ hastscript: 9.0.1
+ web-namespaces: 2.0.1
+
+ hast-util-from-html-isomorphic@2.0.0:
+ dependencies:
+ '@types/hast': 3.0.4
+ hast-util-from-dom: 5.0.1
+ hast-util-from-html: 2.0.3
+ unist-util-remove-position: 5.0.0
+
+ hast-util-from-html@2.0.3:
+ dependencies:
+ '@types/hast': 3.0.4
+ devlop: 1.1.0
+ hast-util-from-parse5: 8.0.3
+ parse5: 7.3.0
+ vfile: 6.0.3
+ vfile-message: 4.0.3
+
+ hast-util-from-parse5@8.0.3:
+ dependencies:
+ '@types/hast': 3.0.4
+ '@types/unist': 3.0.3
+ devlop: 1.1.0
+ hastscript: 9.0.1
+ property-information: 7.1.0
+ vfile: 6.0.3
+ vfile-location: 5.0.3
+ web-namespaces: 2.0.1
+
+ hast-util-is-element@3.0.0:
+ dependencies:
+ '@types/hast': 3.0.4
+
+ hast-util-parse-selector@4.0.0:
+ dependencies:
+ '@types/hast': 3.0.4
+
+ hast-util-raw@9.1.0:
+ dependencies:
+ '@types/hast': 3.0.4
+ '@types/unist': 3.0.3
+ '@ungap/structured-clone': 1.3.0
+ hast-util-from-parse5: 8.0.3
+ hast-util-to-parse5: 8.0.1
+ html-void-elements: 3.0.0
+ mdast-util-to-hast: 13.2.1
+ parse5: 7.3.0
+ unist-util-position: 5.0.0
+ unist-util-visit: 5.0.0
+ vfile: 6.0.3
+ web-namespaces: 2.0.1
+ zwitch: 2.0.4
+
+ hast-util-to-html@9.0.5:
+ dependencies:
+ '@types/hast': 3.0.4
+ '@types/unist': 3.0.3
+ ccount: 2.0.1
+ comma-separated-tokens: 2.0.3
+ hast-util-whitespace: 3.0.0
+ html-void-elements: 3.0.0
+ mdast-util-to-hast: 13.2.1
+ property-information: 7.1.0
+ space-separated-tokens: 2.0.2
+ stringify-entities: 4.0.4
+ zwitch: 2.0.4
+
+ hast-util-to-jsx-runtime@2.3.6:
+ dependencies:
+ '@types/estree': 1.0.8
+ '@types/hast': 3.0.4
+ '@types/unist': 3.0.3
+ comma-separated-tokens: 2.0.3
+ devlop: 1.1.0
+ estree-util-is-identifier-name: 3.0.0
+ hast-util-whitespace: 3.0.0
+ mdast-util-mdx-expression: 2.0.1
+ mdast-util-mdx-jsx: 3.2.0
+ mdast-util-mdxjs-esm: 2.0.1
+ property-information: 7.1.0
+ space-separated-tokens: 2.0.2
+ style-to-js: 1.1.21
+ unist-util-position: 5.0.0
+ vfile-message: 4.0.3
+ transitivePeerDependencies:
+ - supports-color
+
+ hast-util-to-parse5@8.0.1:
+ dependencies:
+ '@types/hast': 3.0.4
+ comma-separated-tokens: 2.0.3
+ devlop: 1.1.0
+ property-information: 7.1.0
+ space-separated-tokens: 2.0.2
+ web-namespaces: 2.0.1
+ zwitch: 2.0.4
+
+ hast-util-to-text@4.0.2:
+ dependencies:
+ '@types/hast': 3.0.4
+ '@types/unist': 3.0.3
+ hast-util-is-element: 3.0.0
+ unist-util-find-after: 5.0.0
+
+ hast-util-whitespace@3.0.0:
+ dependencies:
+ '@types/hast': 3.0.4
+
+ hast@1.0.0: {}
+
+ hastscript@9.0.1:
+ dependencies:
+ '@types/hast': 3.0.4
+ comma-separated-tokens: 2.0.3
+ hast-util-parse-selector: 4.0.0
+ property-information: 7.1.0
+ space-separated-tokens: 2.0.2
+
hermes-estree@0.25.1: {}
hermes-parser@0.25.1:
@@ -9335,10 +10907,18 @@ snapshots:
html-escaper@2.0.2: {}
+ html-url-attributes@3.0.1: {}
+
+ html-void-elements@3.0.0: {}
+
human-signals@2.1.0: {}
husky@9.1.7: {}
+ iconv-lite@0.6.3:
+ dependencies:
+ safer-buffer: 2.1.2
+
ignore@5.3.2: {}
ignore@7.0.5: {}
@@ -9364,6 +10944,8 @@ snapshots:
inherits@2.0.4: {}
+ inline-style-parser@0.2.7: {}
+
input-otp@1.4.2(react-dom@19.2.1(react@19.2.1))(react@19.2.1):
dependencies:
react: 19.2.1
@@ -9375,6 +10957,8 @@ snapshots:
hasown: 2.0.2
side-channel: 1.1.0
+ internmap@1.0.1: {}
+
internmap@2.0.3: {}
intersection-observer@0.12.2: {}
@@ -9386,6 +10970,13 @@ snapshots:
'@formatjs/icu-messageformat-parser': 2.11.3
tslib: 2.8.1
+ is-alphabetical@2.0.1: {}
+
+ is-alphanumerical@2.0.1:
+ dependencies:
+ is-alphabetical: 2.0.1
+ is-decimal: 2.0.1
+
is-array-buffer@3.0.5:
dependencies:
call-bind: 1.0.8
@@ -9432,6 +11023,8 @@ snapshots:
call-bound: 1.0.4
has-tostringtag: 1.0.2
+ is-decimal@2.0.1: {}
+
is-extglob@2.1.1: {}
is-finalizationregistry@1.1.1:
@@ -9459,6 +11052,8 @@ snapshots:
dependencies:
is-extglob: 2.1.1
+ is-hexadecimal@2.0.1: {}
+
is-map@2.0.3: {}
is-negative-zero@2.0.3: {}
@@ -9470,6 +11065,8 @@ snapshots:
is-number@7.0.0: {}
+ is-plain-obj@4.1.0: {}
+
is-plain-object@5.0.0: {}
is-regex@1.2.1:
@@ -9899,6 +11496,8 @@ snapshots:
json-schema-traverse@0.4.1: {}
+ json-schema@0.4.0: {}
+
json-stable-stringify-without-jsonify@1.0.1: {}
json5@1.0.2:
@@ -9916,16 +11515,34 @@ snapshots:
object.assign: 4.1.7
object.values: 1.2.1
+ katex@0.16.25:
+ dependencies:
+ commander: 8.3.0
+
keyv@4.5.4:
dependencies:
json-buffer: 3.0.1
+ khroma@2.1.0: {}
+
+ langium@3.3.1:
+ dependencies:
+ chevrotain: 11.0.3
+ chevrotain-allstar: 0.3.1(chevrotain@11.0.3)
+ vscode-languageserver: 9.0.1
+ vscode-languageserver-textdocument: 1.0.12
+ vscode-uri: 3.0.8
+
language-subtag-registry@0.3.23: {}
language-tags@1.0.9:
dependencies:
language-subtag-registry: 0.3.23
+ layout-base@1.0.2: {}
+
+ layout-base@2.0.1: {}
+
leven@3.1.0: {}
levn@0.4.1:
@@ -10014,6 +11631,8 @@ snapshots:
dependencies:
p-locate: 5.0.0
+ lodash-es@4.17.21: {}
+
lodash.merge@4.6.2: {}
lodash@4.17.21: {}
@@ -10026,6 +11645,8 @@ snapshots:
strip-ansi: 7.1.0
wrap-ansi: 9.0.0
+ longest-streak@3.1.0: {}
+
loose-envify@1.4.0:
dependencies:
js-tokens: 4.0.0
@@ -10042,6 +11663,10 @@ snapshots:
dependencies:
react: 19.2.1
+ lucide-react@0.542.0(react@19.2.1):
+ dependencies:
+ react: 19.2.1
+
lz-string@1.5.0: {}
magic-string@0.30.19:
@@ -10058,12 +11683,437 @@ snapshots:
dependencies:
tmpl: 1.0.5
+ markdown-table@3.0.4: {}
+
+ marked@16.4.2: {}
+
math-intrinsics@1.1.0: {}
+ mdast-util-find-and-replace@3.0.2:
+ dependencies:
+ '@types/mdast': 4.0.4
+ escape-string-regexp: 5.0.0
+ unist-util-is: 6.0.1
+ unist-util-visit-parents: 6.0.2
+
+ mdast-util-from-markdown@2.0.2:
+ dependencies:
+ '@types/mdast': 4.0.4
+ '@types/unist': 3.0.3
+ decode-named-character-reference: 1.2.0
+ devlop: 1.1.0
+ mdast-util-to-string: 4.0.0
+ micromark: 4.0.2
+ micromark-util-decode-numeric-character-reference: 2.0.2
+ micromark-util-decode-string: 2.0.1
+ micromark-util-normalize-identifier: 2.0.1
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.2
+ unist-util-stringify-position: 4.0.0
+ transitivePeerDependencies:
+ - supports-color
+
+ mdast-util-gfm-autolink-literal@2.0.1:
+ dependencies:
+ '@types/mdast': 4.0.4
+ ccount: 2.0.1
+ devlop: 1.1.0
+ mdast-util-find-and-replace: 3.0.2
+ micromark-util-character: 2.1.1
+
+ mdast-util-gfm-footnote@2.1.0:
+ dependencies:
+ '@types/mdast': 4.0.4
+ devlop: 1.1.0
+ mdast-util-from-markdown: 2.0.2
+ mdast-util-to-markdown: 2.1.2
+ micromark-util-normalize-identifier: 2.0.1
+ transitivePeerDependencies:
+ - supports-color
+
+ mdast-util-gfm-strikethrough@2.0.0:
+ dependencies:
+ '@types/mdast': 4.0.4
+ mdast-util-from-markdown: 2.0.2
+ mdast-util-to-markdown: 2.1.2
+ transitivePeerDependencies:
+ - supports-color
+
+ mdast-util-gfm-table@2.0.0:
+ dependencies:
+ '@types/mdast': 4.0.4
+ devlop: 1.1.0
+ markdown-table: 3.0.4
+ mdast-util-from-markdown: 2.0.2
+ mdast-util-to-markdown: 2.1.2
+ transitivePeerDependencies:
+ - supports-color
+
+ mdast-util-gfm-task-list-item@2.0.0:
+ dependencies:
+ '@types/mdast': 4.0.4
+ devlop: 1.1.0
+ mdast-util-from-markdown: 2.0.2
+ mdast-util-to-markdown: 2.1.2
+ transitivePeerDependencies:
+ - supports-color
+
+ mdast-util-gfm@3.1.0:
+ dependencies:
+ mdast-util-from-markdown: 2.0.2
+ mdast-util-gfm-autolink-literal: 2.0.1
+ mdast-util-gfm-footnote: 2.1.0
+ mdast-util-gfm-strikethrough: 2.0.0
+ mdast-util-gfm-table: 2.0.0
+ mdast-util-gfm-task-list-item: 2.0.0
+ mdast-util-to-markdown: 2.1.2
+ transitivePeerDependencies:
+ - supports-color
+
+ mdast-util-math@3.0.0:
+ dependencies:
+ '@types/hast': 3.0.4
+ '@types/mdast': 4.0.4
+ devlop: 1.1.0
+ longest-streak: 3.1.0
+ mdast-util-from-markdown: 2.0.2
+ mdast-util-to-markdown: 2.1.2
+ unist-util-remove-position: 5.0.0
+ transitivePeerDependencies:
+ - supports-color
+
+ mdast-util-mdx-expression@2.0.1:
+ dependencies:
+ '@types/estree-jsx': 1.0.5
+ '@types/hast': 3.0.4
+ '@types/mdast': 4.0.4
+ devlop: 1.1.0
+ mdast-util-from-markdown: 2.0.2
+ mdast-util-to-markdown: 2.1.2
+ transitivePeerDependencies:
+ - supports-color
+
+ mdast-util-mdx-jsx@3.2.0:
+ dependencies:
+ '@types/estree-jsx': 1.0.5
+ '@types/hast': 3.0.4
+ '@types/mdast': 4.0.4
+ '@types/unist': 3.0.3
+ ccount: 2.0.1
+ devlop: 1.1.0
+ mdast-util-from-markdown: 2.0.2
+ mdast-util-to-markdown: 2.1.2
+ parse-entities: 4.0.2
+ stringify-entities: 4.0.4
+ unist-util-stringify-position: 4.0.0
+ vfile-message: 4.0.3
+ transitivePeerDependencies:
+ - supports-color
+
+ mdast-util-mdxjs-esm@2.0.1:
+ dependencies:
+ '@types/estree-jsx': 1.0.5
+ '@types/hast': 3.0.4
+ '@types/mdast': 4.0.4
+ devlop: 1.1.0
+ mdast-util-from-markdown: 2.0.2
+ mdast-util-to-markdown: 2.1.2
+ transitivePeerDependencies:
+ - supports-color
+
+ mdast-util-phrasing@4.1.0:
+ dependencies:
+ '@types/mdast': 4.0.4
+ unist-util-is: 6.0.1
+
+ mdast-util-to-hast@13.2.1:
+ dependencies:
+ '@types/hast': 3.0.4
+ '@types/mdast': 4.0.4
+ '@ungap/structured-clone': 1.3.0
+ devlop: 1.1.0
+ micromark-util-sanitize-uri: 2.0.1
+ trim-lines: 3.0.1
+ unist-util-position: 5.0.0
+ unist-util-visit: 5.0.0
+ vfile: 6.0.3
+
+ mdast-util-to-markdown@2.1.2:
+ dependencies:
+ '@types/mdast': 4.0.4
+ '@types/unist': 3.0.3
+ longest-streak: 3.1.0
+ mdast-util-phrasing: 4.1.0
+ mdast-util-to-string: 4.0.0
+ micromark-util-classify-character: 2.0.1
+ micromark-util-decode-string: 2.0.1
+ unist-util-visit: 5.0.0
+ zwitch: 2.0.4
+
+ mdast-util-to-string@4.0.0:
+ dependencies:
+ '@types/mdast': 4.0.4
+
merge-stream@2.0.0: {}
merge2@1.4.1: {}
+ mermaid@11.12.2:
+ dependencies:
+ '@braintree/sanitize-url': 7.1.1
+ '@iconify/utils': 3.1.0
+ '@mermaid-js/parser': 0.6.3
+ '@types/d3': 7.4.3
+ cytoscape: 3.33.1
+ cytoscape-cose-bilkent: 4.1.0(cytoscape@3.33.1)
+ cytoscape-fcose: 2.2.0(cytoscape@3.33.1)
+ d3: 7.9.0
+ d3-sankey: 0.12.3
+ dagre-d3-es: 7.0.13
+ dayjs: 1.11.19
+ dompurify: 3.3.0
+ katex: 0.16.25
+ khroma: 2.1.0
+ lodash-es: 4.17.21
+ marked: 16.4.2
+ roughjs: 4.6.6
+ stylis: 4.3.6
+ ts-dedent: 2.2.0
+ uuid: 11.1.0
+
+ micromark-core-commonmark@2.0.3:
+ dependencies:
+ decode-named-character-reference: 1.2.0
+ devlop: 1.1.0
+ micromark-factory-destination: 2.0.1
+ micromark-factory-label: 2.0.1
+ micromark-factory-space: 2.0.1
+ micromark-factory-title: 2.0.1
+ micromark-factory-whitespace: 2.0.1
+ micromark-util-character: 2.1.1
+ micromark-util-chunked: 2.0.1
+ micromark-util-classify-character: 2.0.1
+ micromark-util-html-tag-name: 2.0.1
+ micromark-util-normalize-identifier: 2.0.1
+ micromark-util-resolve-all: 2.0.1
+ micromark-util-subtokenize: 2.1.0
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.2
+
+ micromark-extension-cjk-friendly-gfm-strikethrough@1.2.3(micromark-util-types@2.0.2)(micromark@4.0.2):
+ dependencies:
+ devlop: 1.1.0
+ get-east-asian-width: 1.3.0
+ micromark: 4.0.2
+ micromark-extension-cjk-friendly-util: 2.1.1(micromark-util-types@2.0.2)
+ micromark-util-character: 2.1.1
+ micromark-util-chunked: 2.0.1
+ micromark-util-resolve-all: 2.0.1
+ micromark-util-symbol: 2.0.1
+ optionalDependencies:
+ micromark-util-types: 2.0.2
+
+ micromark-extension-cjk-friendly-util@2.1.1(micromark-util-types@2.0.2):
+ dependencies:
+ get-east-asian-width: 1.3.0
+ micromark-util-character: 2.1.1
+ micromark-util-symbol: 2.0.1
+ optionalDependencies:
+ micromark-util-types: 2.0.2
+
+ micromark-extension-cjk-friendly@1.2.3(micromark-util-types@2.0.2)(micromark@4.0.2):
+ dependencies:
+ devlop: 1.1.0
+ micromark: 4.0.2
+ micromark-extension-cjk-friendly-util: 2.1.1(micromark-util-types@2.0.2)
+ micromark-util-chunked: 2.0.1
+ micromark-util-resolve-all: 2.0.1
+ micromark-util-symbol: 2.0.1
+ optionalDependencies:
+ micromark-util-types: 2.0.2
+
+ micromark-extension-gfm-autolink-literal@2.1.0:
+ dependencies:
+ micromark-util-character: 2.1.1
+ micromark-util-sanitize-uri: 2.0.1
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.2
+
+ micromark-extension-gfm-footnote@2.1.0:
+ dependencies:
+ devlop: 1.1.0
+ micromark-core-commonmark: 2.0.3
+ micromark-factory-space: 2.0.1
+ micromark-util-character: 2.1.1
+ micromark-util-normalize-identifier: 2.0.1
+ micromark-util-sanitize-uri: 2.0.1
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.2
+
+ micromark-extension-gfm-strikethrough@2.1.0:
+ dependencies:
+ devlop: 1.1.0
+ micromark-util-chunked: 2.0.1
+ micromark-util-classify-character: 2.0.1
+ micromark-util-resolve-all: 2.0.1
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.2
+
+ micromark-extension-gfm-table@2.1.1:
+ dependencies:
+ devlop: 1.1.0
+ micromark-factory-space: 2.0.1
+ micromark-util-character: 2.1.1
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.2
+
+ micromark-extension-gfm-tagfilter@2.0.0:
+ dependencies:
+ micromark-util-types: 2.0.2
+
+ micromark-extension-gfm-task-list-item@2.1.0:
+ dependencies:
+ devlop: 1.1.0
+ micromark-factory-space: 2.0.1
+ micromark-util-character: 2.1.1
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.2
+
+ micromark-extension-gfm@3.0.0:
+ dependencies:
+ micromark-extension-gfm-autolink-literal: 2.1.0
+ micromark-extension-gfm-footnote: 2.1.0
+ micromark-extension-gfm-strikethrough: 2.1.0
+ micromark-extension-gfm-table: 2.1.1
+ micromark-extension-gfm-tagfilter: 2.0.0
+ micromark-extension-gfm-task-list-item: 2.1.0
+ micromark-util-combine-extensions: 2.0.1
+ micromark-util-types: 2.0.2
+
+ micromark-extension-math@3.1.0:
+ dependencies:
+ '@types/katex': 0.16.7
+ devlop: 1.1.0
+ katex: 0.16.25
+ micromark-factory-space: 2.0.1
+ micromark-util-character: 2.1.1
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.2
+
+ micromark-factory-destination@2.0.1:
+ dependencies:
+ micromark-util-character: 2.1.1
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.2
+
+ micromark-factory-label@2.0.1:
+ dependencies:
+ devlop: 1.1.0
+ micromark-util-character: 2.1.1
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.2
+
+ micromark-factory-space@2.0.1:
+ dependencies:
+ micromark-util-character: 2.1.1
+ micromark-util-types: 2.0.2
+
+ micromark-factory-title@2.0.1:
+ dependencies:
+ micromark-factory-space: 2.0.1
+ micromark-util-character: 2.1.1
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.2
+
+ micromark-factory-whitespace@2.0.1:
+ dependencies:
+ micromark-factory-space: 2.0.1
+ micromark-util-character: 2.1.1
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.2
+
+ micromark-util-character@2.1.1:
+ dependencies:
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.2
+
+ micromark-util-chunked@2.0.1:
+ dependencies:
+ micromark-util-symbol: 2.0.1
+
+ micromark-util-classify-character@2.0.1:
+ dependencies:
+ micromark-util-character: 2.1.1
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.2
+
+ micromark-util-combine-extensions@2.0.1:
+ dependencies:
+ micromark-util-chunked: 2.0.1
+ micromark-util-types: 2.0.2
+
+ micromark-util-decode-numeric-character-reference@2.0.2:
+ dependencies:
+ micromark-util-symbol: 2.0.1
+
+ micromark-util-decode-string@2.0.1:
+ dependencies:
+ decode-named-character-reference: 1.2.0
+ micromark-util-character: 2.1.1
+ micromark-util-decode-numeric-character-reference: 2.0.2
+ micromark-util-symbol: 2.0.1
+
+ micromark-util-encode@2.0.1: {}
+
+ micromark-util-html-tag-name@2.0.1: {}
+
+ micromark-util-normalize-identifier@2.0.1:
+ dependencies:
+ micromark-util-symbol: 2.0.1
+
+ micromark-util-resolve-all@2.0.1:
+ dependencies:
+ micromark-util-types: 2.0.2
+
+ micromark-util-sanitize-uri@2.0.1:
+ dependencies:
+ micromark-util-character: 2.1.1
+ micromark-util-encode: 2.0.1
+ micromark-util-symbol: 2.0.1
+
+ micromark-util-subtokenize@2.1.0:
+ dependencies:
+ devlop: 1.1.0
+ micromark-util-chunked: 2.0.1
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.2
+
+ micromark-util-symbol@2.0.1: {}
+
+ micromark-util-types@2.0.2: {}
+
+ micromark@4.0.2:
+ dependencies:
+ '@types/debug': 4.1.12
+ debug: 4.4.3
+ decode-named-character-reference: 1.2.0
+ devlop: 1.1.0
+ micromark-core-commonmark: 2.0.3
+ micromark-factory-space: 2.0.1
+ micromark-util-character: 2.1.1
+ micromark-util-chunked: 2.0.1
+ micromark-util-combine-extensions: 2.0.1
+ micromark-util-decode-numeric-character-reference: 2.0.2
+ micromark-util-encode: 2.0.1
+ micromark-util-normalize-identifier: 2.0.1
+ micromark-util-resolve-all: 2.0.1
+ micromark-util-sanitize-uri: 2.0.1
+ micromark-util-subtokenize: 2.1.0
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.2
+ transitivePeerDependencies:
+ - supports-color
+
micromatch@4.0.8:
dependencies:
braces: 3.0.3
@@ -10091,6 +12141,27 @@ snapshots:
dependencies:
minipass: 7.1.2
+ mlly@1.8.0:
+ dependencies:
+ acorn: 8.15.0
+ pathe: 2.0.3
+ pkg-types: 1.3.1
+ ufo: 1.6.1
+
+ motion-dom@12.23.23:
+ dependencies:
+ motion-utils: 12.23.6
+
+ motion-utils@12.23.6: {}
+
+ motion@12.23.25(react-dom@19.2.1(react@19.2.1))(react@19.2.1):
+ dependencies:
+ framer-motion: 12.23.25(react-dom@19.2.1(react@19.2.1))(react@19.2.1)
+ tslib: 2.8.1
+ optionalDependencies:
+ react: 19.2.1
+ react-dom: 19.2.1(react@19.2.1)
+
mrmime@2.0.1: {}
ms@2.1.3: {}
@@ -10099,6 +12170,8 @@ snapshots:
nanoid@3.3.11: {}
+ nanoid@5.1.6: {}
+
napi-postinstall@0.3.3: {}
natural-compare@1.4.0: {}
@@ -10107,12 +12180,12 @@ snapshots:
next-intl-swc-plugin-extractor@4.5.8: {}
- next-intl@4.5.8(next@16.0.7(@babel/core@7.28.3)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(react@19.2.1)(typescript@5.9.3):
+ next-intl@4.5.8(next@16.0.7(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(react@19.2.1)(typescript@5.9.3):
dependencies:
'@formatjs/intl-localematcher': 0.5.10
'@swc/core': 1.15.3
negotiator: 1.0.0
- next: 16.0.7(@babel/core@7.28.3)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)
+ next: 16.0.7(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)
next-intl-swc-plugin-extractor: 4.5.8
po-parser: 1.0.2
react: 19.2.1
@@ -10127,7 +12200,7 @@ snapshots:
react: 19.2.1
react-dom: 19.2.1(react@19.2.1)
- next@16.0.7(@babel/core@7.28.3)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1):
+ next@16.0.7(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1):
dependencies:
'@next/env': 16.0.7
'@swc/helpers': 0.5.15
@@ -10145,6 +12218,7 @@ snapshots:
'@next/swc-linux-x64-musl': 16.0.7
'@next/swc-win32-arm64-msvc': 16.0.7
'@next/swc-win32-x64-msvc': 16.0.7
+ '@opentelemetry/api': 1.9.0
babel-plugin-react-compiler: 1.0.0
sharp: 0.34.5
transitivePeerDependencies:
@@ -10217,6 +12291,14 @@ snapshots:
dependencies:
mimic-function: 5.0.1
+ oniguruma-parser@0.12.1: {}
+
+ oniguruma-to-es@4.3.4:
+ dependencies:
+ oniguruma-parser: 0.12.1
+ regex: 6.0.1
+ regex-recursion: 6.0.2
+
opener@1.5.2: {}
optionator@0.9.4:
@@ -10265,10 +12347,22 @@ snapshots:
package-json-from-dist@1.0.1: {}
+ package-manager-detector@1.6.0: {}
+
parent-module@1.0.1:
dependencies:
callsites: 3.1.0
+ parse-entities@4.0.2:
+ dependencies:
+ '@types/unist': 2.0.11
+ character-entities-legacy: 3.0.0
+ character-reference-invalid: 2.0.1
+ decode-named-character-reference: 1.2.0
+ is-alphanumerical: 2.0.1
+ is-decimal: 2.0.1
+ is-hexadecimal: 2.0.1
+
parse-json@5.2.0:
dependencies:
'@babel/code-frame': 7.27.1
@@ -10276,6 +12370,12 @@ snapshots:
json-parse-even-better-errors: 2.3.1
lines-and-columns: 1.2.4
+ parse5@7.3.0:
+ dependencies:
+ entities: 6.0.1
+
+ path-data-parser@0.1.0: {}
+
path-exists@4.0.0: {}
path-is-absolute@1.0.1: {}
@@ -10289,6 +12389,8 @@ snapshots:
lru-cache: 10.4.3
minipass: 7.1.2
+ pathe@2.0.3: {}
+
pathval@2.0.1: {}
picocolors@1.1.1: {}
@@ -10305,8 +12407,21 @@ snapshots:
dependencies:
find-up: 4.1.0
+ pkg-types@1.3.1:
+ dependencies:
+ confbox: 0.1.8
+ mlly: 1.8.0
+ pathe: 2.0.3
+
po-parser@1.0.2: {}
+ points-on-curve@0.2.0: {}
+
+ points-on-path@0.2.1:
+ dependencies:
+ path-data-parser: 0.1.0
+ points-on-curve: 0.2.0
+
possible-typed-array-names@1.1.0: {}
postcss-value-parser@4.2.0: {}
@@ -10346,6 +12461,8 @@ snapshots:
object-assign: 4.1.1
react-is: 16.13.1
+ property-information@7.1.0: {}
+
punycode@2.3.1: {}
pure-rand@7.0.1: {}
@@ -10487,6 +12604,16 @@ snapshots:
get-proto: 1.0.1
which-builtin-type: 1.2.1
+ regex-recursion@6.0.2:
+ dependencies:
+ regex-utilities: 2.3.0
+
+ regex-utilities@2.3.0: {}
+
+ regex@6.0.1:
+ dependencies:
+ regex-utilities: 2.3.0
+
regexp.prototype.flags@1.5.4:
dependencies:
call-bind: 1.0.8
@@ -10496,6 +12623,91 @@ snapshots:
gopd: 1.2.0
set-function-name: 2.0.2
+ rehype-harden@1.1.6:
+ dependencies:
+ unist-util-visit: 5.0.0
+
+ rehype-katex@7.0.1:
+ dependencies:
+ '@types/hast': 3.0.4
+ '@types/katex': 0.16.7
+ hast-util-from-html-isomorphic: 2.0.0
+ hast-util-to-text: 4.0.2
+ katex: 0.16.25
+ unist-util-visit-parents: 6.0.2
+ vfile: 6.0.3
+
+ rehype-raw@7.0.0:
+ dependencies:
+ '@types/hast': 3.0.4
+ hast-util-raw: 9.1.0
+ vfile: 6.0.3
+
+ remark-cjk-friendly-gfm-strikethrough@1.2.3(@types/mdast@4.0.4)(micromark-util-types@2.0.2)(micromark@4.0.2)(unified@11.0.5):
+ dependencies:
+ micromark-extension-cjk-friendly-gfm-strikethrough: 1.2.3(micromark-util-types@2.0.2)(micromark@4.0.2)
+ unified: 11.0.5
+ optionalDependencies:
+ '@types/mdast': 4.0.4
+ transitivePeerDependencies:
+ - micromark
+ - micromark-util-types
+
+ remark-cjk-friendly@1.2.3(@types/mdast@4.0.4)(micromark-util-types@2.0.2)(micromark@4.0.2)(unified@11.0.5):
+ dependencies:
+ micromark-extension-cjk-friendly: 1.2.3(micromark-util-types@2.0.2)(micromark@4.0.2)
+ unified: 11.0.5
+ optionalDependencies:
+ '@types/mdast': 4.0.4
+ transitivePeerDependencies:
+ - micromark
+ - micromark-util-types
+
+ remark-gfm@4.0.1:
+ dependencies:
+ '@types/mdast': 4.0.4
+ mdast-util-gfm: 3.1.0
+ micromark-extension-gfm: 3.0.0
+ remark-parse: 11.0.0
+ remark-stringify: 11.0.0
+ unified: 11.0.5
+ transitivePeerDependencies:
+ - supports-color
+
+ remark-math@6.0.0:
+ dependencies:
+ '@types/mdast': 4.0.4
+ mdast-util-math: 3.0.0
+ micromark-extension-math: 3.1.0
+ unified: 11.0.5
+ transitivePeerDependencies:
+ - supports-color
+
+ remark-parse@11.0.0:
+ dependencies:
+ '@types/mdast': 4.0.4
+ mdast-util-from-markdown: 2.0.2
+ micromark-util-types: 2.0.2
+ unified: 11.0.5
+ transitivePeerDependencies:
+ - supports-color
+
+ remark-rehype@11.1.2:
+ dependencies:
+ '@types/hast': 3.0.4
+ '@types/mdast': 4.0.4
+ mdast-util-to-hast: 13.2.1
+ unified: 11.0.5
+ vfile: 6.0.3
+
+ remark-stringify@11.0.0:
+ dependencies:
+ '@types/mdast': 4.0.4
+ mdast-util-to-markdown: 2.1.2
+ unified: 11.0.5
+
+ remend@1.0.1: {}
+
require-directory@2.1.1: {}
resize-observer-polyfill@1.5.1: {}
@@ -10531,6 +12743,8 @@ snapshots:
rfdc@1.4.1: {}
+ robust-predicates@3.0.2: {}
+
rollup@4.52.5:
dependencies:
'@types/estree': 1.0.8
@@ -10559,10 +12773,19 @@ snapshots:
'@rollup/rollup-win32-x64-msvc': 4.52.5
fsevents: 2.3.3
+ roughjs@4.6.6:
+ dependencies:
+ hachure-fill: 0.5.2
+ path-data-parser: 0.1.0
+ points-on-curve: 0.2.0
+ points-on-path: 0.2.1
+
run-parallel@1.2.0:
dependencies:
queue-microtask: 1.2.3
+ rw@1.3.3: {}
+
safe-array-concat@1.1.3:
dependencies:
call-bind: 1.0.8
@@ -10582,6 +12805,8 @@ snapshots:
es-errors: 1.3.0
is-regex: 1.2.1
+ safer-buffer@2.1.2: {}
+
scheduler@0.27.0: {}
screenfull@5.2.0: {}
@@ -10654,6 +12879,17 @@ snapshots:
shebang-regex@3.0.0: {}
+ shiki@3.19.0:
+ dependencies:
+ '@shikijs/core': 3.19.0
+ '@shikijs/engine-javascript': 3.19.0
+ '@shikijs/engine-oniguruma': 3.19.0
+ '@shikijs/langs': 3.19.0
+ '@shikijs/themes': 3.19.0
+ '@shikijs/types': 3.19.0
+ '@shikijs/vscode-textmate': 10.0.2
+ '@types/hast': 3.0.4
+
side-channel-list@1.0.0:
dependencies:
es-errors: 1.3.0
@@ -10724,6 +12960,8 @@ snapshots:
source-map@0.6.1: {}
+ space-separated-tokens@2.0.2: {}
+
sprintf-js@1.0.3: {}
stable-hash@0.0.5: {}
@@ -10761,6 +12999,37 @@ snapshots:
- utf-8-validate
- vite
+ streamdown@1.6.10(@types/mdast@4.0.4)(micromark-util-types@2.0.2)(micromark@4.0.2)(react@19.2.1):
+ dependencies:
+ clsx: 2.1.1
+ hast: 1.0.0
+ hast-util-to-jsx-runtime: 2.3.6
+ html-url-attributes: 3.0.1
+ katex: 0.16.25
+ lucide-react: 0.542.0(react@19.2.1)
+ marked: 16.4.2
+ mermaid: 11.12.2
+ react: 19.2.1
+ rehype-harden: 1.1.6
+ rehype-katex: 7.0.1
+ rehype-raw: 7.0.0
+ remark-cjk-friendly: 1.2.3(@types/mdast@4.0.4)(micromark-util-types@2.0.2)(micromark@4.0.2)(unified@11.0.5)
+ remark-cjk-friendly-gfm-strikethrough: 1.2.3(@types/mdast@4.0.4)(micromark-util-types@2.0.2)(micromark@4.0.2)(unified@11.0.5)
+ remark-gfm: 4.0.1
+ remark-math: 6.0.0
+ remark-parse: 11.0.0
+ remark-rehype: 11.1.2
+ remend: 1.0.1
+ shiki: 3.19.0
+ tailwind-merge: 3.4.0
+ unified: 11.0.5
+ unist-util-visit: 5.0.0
+ transitivePeerDependencies:
+ - '@types/mdast'
+ - micromark
+ - micromark-util-types
+ - supports-color
+
string-argv@0.3.2: {}
string-length@4.0.2:
@@ -10836,6 +13105,11 @@ snapshots:
define-properties: 1.2.1
es-object-atoms: 1.1.1
+ stringify-entities@4.0.4:
+ dependencies:
+ character-entities-html4: 2.1.0
+ character-entities-legacy: 3.0.0
+
strip-ansi@6.0.1:
dependencies:
ansi-regex: 5.0.1
@@ -10858,6 +13132,14 @@ snapshots:
strip-json-comments@3.1.1: {}
+ style-to-js@1.1.21:
+ dependencies:
+ style-to-object: 1.0.14
+
+ style-to-object@1.0.14:
+ dependencies:
+ inline-style-parser: 0.2.7
+
styled-jsx@5.1.6(@babel/core@7.28.3)(react@19.2.1):
dependencies:
client-only: 0.0.1
@@ -10865,6 +13147,8 @@ snapshots:
optionalDependencies:
'@babel/core': 7.28.3
+ stylis@4.3.6: {}
+
supports-color@7.2.0:
dependencies:
has-flag: 4.0.0
@@ -10917,6 +13201,8 @@ snapshots:
tiny-invariant@1.3.3: {}
+ tinyexec@1.0.2: {}
+
tinyglobby@0.2.15:
dependencies:
fdir: 6.5.0(picomatch@4.0.3)
@@ -10932,8 +13218,19 @@ snapshots:
dependencies:
is-number: 7.0.0
+ tokenlens@1.3.1:
+ dependencies:
+ '@tokenlens/core': 1.3.0
+ '@tokenlens/fetch': 1.3.0
+ '@tokenlens/helpers': 1.3.1
+ '@tokenlens/models': 1.3.0
+
totalist@3.0.1: {}
+ trim-lines@3.0.1: {}
+
+ trough@2.2.0: {}
+
ts-api-utils@2.1.0(typescript@5.9.3):
dependencies:
typescript: 5.9.3
@@ -11031,6 +13328,8 @@ snapshots:
typescript@5.9.3: {}
+ ufo@1.6.1: {}
+
unbox-primitive@1.1.0:
dependencies:
call-bound: 1.0.4
@@ -11042,6 +13341,49 @@ snapshots:
undici-types@7.16.0: {}
+ unified@11.0.5:
+ dependencies:
+ '@types/unist': 3.0.3
+ bail: 2.0.2
+ devlop: 1.1.0
+ extend: 3.0.2
+ is-plain-obj: 4.1.0
+ trough: 2.2.0
+ vfile: 6.0.3
+
+ unist-util-find-after@5.0.0:
+ dependencies:
+ '@types/unist': 3.0.3
+ unist-util-is: 6.0.1
+
+ unist-util-is@6.0.1:
+ dependencies:
+ '@types/unist': 3.0.3
+
+ unist-util-position@5.0.0:
+ dependencies:
+ '@types/unist': 3.0.3
+
+ unist-util-remove-position@5.0.0:
+ dependencies:
+ '@types/unist': 3.0.3
+ unist-util-visit: 5.0.0
+
+ unist-util-stringify-position@4.0.0:
+ dependencies:
+ '@types/unist': 3.0.3
+
+ unist-util-visit-parents@6.0.2:
+ dependencies:
+ '@types/unist': 3.0.3
+ unist-util-is: 6.0.1
+
+ unist-util-visit@5.0.0:
+ dependencies:
+ '@types/unist': 3.0.3
+ unist-util-is: 6.0.1
+ unist-util-visit-parents: 6.0.2
+
unplugin@2.3.10:
dependencies:
'@jridgewell/remapping': 2.3.5
@@ -11105,10 +13447,16 @@ snapshots:
optionalDependencies:
'@types/react': 19.2.7
+ use-stick-to-bottom@1.1.1(react@19.2.1):
+ dependencies:
+ react: 19.2.1
+
use-sync-external-store@1.6.0(react@19.2.1):
dependencies:
react: 19.2.1
+ uuid@11.1.0: {}
+
v8-compile-cache-lib@3.0.1: {}
v8-to-istanbul@9.3.0:
@@ -11126,6 +13474,21 @@ snapshots:
- '@types/react'
- '@types/react-dom'
+ vfile-location@5.0.3:
+ dependencies:
+ '@types/unist': 3.0.3
+ vfile: 6.0.3
+
+ vfile-message@4.0.3:
+ dependencies:
+ '@types/unist': 3.0.3
+ unist-util-stringify-position: 4.0.0
+
+ vfile@6.0.3:
+ dependencies:
+ '@types/unist': 3.0.3
+ vfile-message: 4.0.3
+
victory-vendor@36.9.2:
dependencies:
'@types/d3-array': 3.2.2
@@ -11159,10 +13522,29 @@ snapshots:
terser: 5.44.0
yaml: 2.8.1
+ vscode-jsonrpc@8.2.0: {}
+
+ vscode-languageserver-protocol@3.17.5:
+ dependencies:
+ vscode-jsonrpc: 8.2.0
+ vscode-languageserver-types: 3.17.5
+
+ vscode-languageserver-textdocument@1.0.12: {}
+
+ vscode-languageserver-types@3.17.5: {}
+
+ vscode-languageserver@9.0.1:
+ dependencies:
+ vscode-languageserver-protocol: 3.17.5
+
+ vscode-uri@3.0.8: {}
+
walker@1.0.8:
dependencies:
makeerror: 1.0.12
+ web-namespaces@2.0.1: {}
+
webpack-bundle-analyzer@4.10.1:
dependencies:
'@discoveryjs/json-ext': 0.5.7
@@ -11296,8 +13678,17 @@ snapshots:
zod@3.25.76: {}
+ zustand@4.5.7(@types/react@19.2.7)(react@19.2.1):
+ dependencies:
+ use-sync-external-store: 1.6.0(react@19.2.1)
+ optionalDependencies:
+ '@types/react': 19.2.7
+ react: 19.2.1
+
zustand@5.0.8(@types/react@19.2.7)(react@19.2.1)(use-sync-external-store@1.6.0(react@19.2.1)):
optionalDependencies:
'@types/react': 19.2.7
react: 19.2.1
use-sync-external-store: 1.6.0(react@19.2.1)
+
+ zwitch@2.0.4: {}