diff --git a/apps/fabro-web/app/components/event-debug.tsx b/apps/fabro-web/app/components/event-debug.tsx index 1a1436264..2d9ccb8c6 100644 --- a/apps/fabro-web/app/components/event-debug.tsx +++ b/apps/fabro-web/app/components/event-debug.tsx @@ -1,5 +1,4 @@ import { useEffect, useMemo, useState } from "react"; -import { createPortal } from "react-dom"; import { Listbox, ListboxButton, @@ -26,6 +25,7 @@ import { highlightJson, type DebugCategory, } from "./event-debug-helpers"; +import { FloatingTooltip } from "./floating-tooltip"; export function DebugEventRow({ event, @@ -416,19 +416,11 @@ function DnaPopover({ anchorRect: DOMRect; runStart: string | undefined; }) { - if (typeof document === "undefined") return null; const category = debugCategory(event.event); - const left = anchorRect.left + anchorRect.width / 2; - const top = anchorRect.top; - return createPortal( -
+ return ( + {`${debugCategoryLabel(category)} · ${friendlyEventName(event.event)} · ${formatElapsed(event.ts, runStart)}`} -
, - document.body, + ); } @@ -628,20 +620,12 @@ function ThreadDnaPopover({ item: ThreadDnaItem; anchorRect: DOMRect; }) { - if (typeof document === "undefined") return null; - const left = anchorRect.left + anchorRect.width / 2; - const top = anchorRect.top; const elapsed = formatThreadElapsed(item.startMs); const duration = item.durationMs > 0 ? formatThreadDuration(item.durationMs) : "instant"; - return createPortal( -
+ return ( + {`${THREAD_CATEGORY_LABEL[item.category]} · ${item.label} · ${elapsed} · ${duration}`} -
, - document.body, + ); } diff --git a/apps/fabro-web/app/components/floating-tooltip.tsx b/apps/fabro-web/app/components/floating-tooltip.tsx new file mode 100644 index 000000000..3ce087904 --- /dev/null +++ b/apps/fabro-web/app/components/floating-tooltip.tsx @@ -0,0 +1,149 @@ +import { + useLayoutEffect, + useRef, + useState, + type CSSProperties, + type ReactNode, +} from "react"; +import { createPortal } from "react-dom"; + +type FloatingTooltipPlacement = "top" | "bottom"; +type FloatingTooltipSize = { height: number; width: number }; + +const VIEWPORT_MARGIN = 12; +const OFFSET = 8; +const DEFAULT_CLASS_NAME = + "whitespace-nowrap rounded-md bg-panel-alt px-2.5 py-1 text-xs text-fg shadow-lg outline-1 -outline-offset-1 outline-line-strong"; + +function clamp(value: number, min: number, max: number): number { + if (max < min) return (min + max) / 2; + return Math.min(Math.max(value, min), max); +} + +function resolvePlacement( + rect: DOMRect, + placement: FloatingTooltipPlacement, + height: number, + viewportHeight: number, +): FloatingTooltipPlacement { + if (height <= 0) return placement; + + const fitsTop = rect.top - OFFSET - height >= VIEWPORT_MARGIN; + const fitsBottom = rect.bottom + OFFSET + height <= viewportHeight - VIEWPORT_MARGIN; + + if (placement === "top") { + return fitsTop || !fitsBottom ? "top" : "bottom"; + } + return fitsBottom || !fitsTop ? "bottom" : "top"; +} + +function floatingStyle( + rect: DOMRect, + placement: FloatingTooltipPlacement, + size: FloatingTooltipSize, + viewport: FloatingTooltipSize, +): CSSProperties { + const viewportWidth = viewport.width; + const viewportHeight = viewport.height; + const centerX = rect.left + rect.width / 2; + const availableWidth = Math.max(0, viewportWidth - VIEWPORT_MARGIN * 2); + const width = size.width > 0 ? Math.min(size.width, availableWidth) : 0; + const halfWidth = width / 2; + const minCenter = VIEWPORT_MARGIN + halfWidth; + const maxCenter = viewportWidth - VIEWPORT_MARGIN - halfWidth; + const left = width > 0 + ? clamp(centerX, minCenter, maxCenter) + : clamp(centerX, VIEWPORT_MARGIN, viewportWidth - VIEWPORT_MARGIN); + const resolvedPlacement = resolvePlacement(rect, placement, size.height, viewportHeight); + + if (resolvedPlacement === "top") { + const top = size.height > 0 + ? Math.max(VIEWPORT_MARGIN, rect.top - OFFSET - size.height) + : rect.top - OFFSET; + return { + left, + maxWidth: availableWidth, + top, + transform: size.height > 0 ? "translateX(-50%)" : "translate(-50%, -100%)", + }; + } + + const top = size.height > 0 + ? Math.min(viewportHeight - VIEWPORT_MARGIN - size.height, rect.bottom + OFFSET) + : rect.bottom + OFFSET; + return { + left, + maxWidth: availableWidth, + top: Math.max(VIEWPORT_MARGIN, top), + transform: "translateX(-50%)", + }; +} + +function viewportSize(): FloatingTooltipSize { + return { height: window.innerHeight, width: window.innerWidth }; +} + +export function FloatingTooltip({ + rect, + placement, + children, + className = DEFAULT_CLASS_NAME, +}: { + rect: DOMRect; + placement: FloatingTooltipPlacement; + children: ReactNode; + className?: string; +}) { + const ref = useRef(null); + const [size, setSize] = useState({ height: 0, width: 0 }); + const [viewport, setViewport] = useState(() => + typeof window === "undefined" ? { height: 0, width: 0 } : viewportSize(), + ); + + useLayoutEffect(() => { + const node = ref.current; + if (!node) return; + + const updateSize = () => { + const next = node.getBoundingClientRect(); + setSize((prev) => + prev.height === next.height && prev.width === next.width + ? prev + : { height: next.height, width: next.width }, + ); + }; + const updateViewport = () => { + const next = viewportSize(); + setViewport((prev) => + prev.height === next.height && prev.width === next.width ? prev : next, + ); + }; + + updateSize(); + updateViewport(); + const resizeObserver = + typeof ResizeObserver === "undefined" + ? null + : new ResizeObserver(updateSize); + resizeObserver?.observe(node); + window.addEventListener("resize", updateViewport); + return () => { + resizeObserver?.disconnect(); + window.removeEventListener("resize", updateViewport); + }; + }, []); + + if (typeof document === "undefined") return null; + + return createPortal( +
+ {children} +
, + document.body, + ); +} diff --git a/apps/fabro-web/app/components/hover-card-style.ts b/apps/fabro-web/app/components/hover-card-style.ts deleted file mode 100644 index 94afd8c86..000000000 --- a/apps/fabro-web/app/components/hover-card-style.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { CSSProperties } from "react"; - -export function hoverCardStyle(rect: DOMRect): CSSProperties { - const margin = 12; - const top = rect.bottom + 6; - if (rect.left > window.innerWidth / 2) { - return { top, right: Math.max(margin, window.innerWidth - rect.right) }; - } - return { top, left: Math.max(margin, rect.left) }; -} diff --git a/apps/fabro-web/app/components/toast.test.tsx b/apps/fabro-web/app/components/toast.test.tsx index 1adf14217..48f67514d 100644 --- a/apps/fabro-web/app/components/toast.test.tsx +++ b/apps/fabro-web/app/components/toast.test.tsx @@ -1,6 +1,7 @@ -import { afterEach, describe, expect, test } from "bun:test"; +import { afterEach, beforeEach, describe, expect, test } from "bun:test"; import { useEffect } from "react"; import TestRenderer, { act } from "react-test-renderer"; +import { toast as sonnerToast, useSonner } from "sonner"; import { ToastProvider, useToast } from "./toast"; @@ -11,179 +12,146 @@ function textFromNode(node: ReturnType return (node.children ?? []).map(textFromNode).join(""); } -function textFromInstance(node: TestRenderer.ReactTestInstance): string { - return node.children - .map((child) => (typeof child === "string" ? child : textFromInstance(child))) - .join(""); -} - -function PushOnMount({ - toast, +function CaptureToastApi({ onReady, }: { - toast: Parameters["push"]>[0]; onReady?: (api: ReturnType) => void; }) { const api = useToast(); useEffect(() => { onReady?.(api); - api.push(toast); - }, [api, onReady, toast]); + }, [api, onReady]); return null; } -describe("ToastProvider", () => { - afterEach(() => { - delete (globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT; +function SonnerToastText() { + const { toasts } = useSonner(); + + return ( + + {toasts.map((toast) => ( +

+ {typeof toast.title === "function" ? toast.title() : toast.title} +

+ ))} +
+ ); +} + +async function flushSonnerUpdates() { + await act(async () => { + await new Promise((resolve) => setTimeout(resolve, 0)); + await new Promise((resolve) => setTimeout(resolve, 0)); + }); +} + +describe("useToast", () => { + beforeEach(() => { + (globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true; + (globalThis as { requestAnimationFrame?: (callback: FrameRequestCallback) => number }).requestAnimationFrame = ( + callback, + ) => setTimeout(callback, 0) as unknown as number; }); - test("push renders a toast with the message", async () => { - (globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true; + afterEach(() => { + sonnerToast.dismiss(); + delete (globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT; + delete (globalThis as { requestAnimationFrame?: (callback: FrameRequestCallback) => number }).requestAnimationFrame; + }); + test("push renders a Sonner toast with the message", async () => { + let api: ReturnType | null = null; let renderer: TestRenderer.ReactTestRenderer | null = null; await act(async () => { renderer = TestRenderer.create( - - - , + <> + + { + api = value; + }} + /> + , ); }); + await act(async () => { + api!.push({ message: "Run archived." }); + }); + await flushSonnerUpdates(); + expect(textFromNode(renderer!.toJSON())).toContain("Run archived."); - const liveRegions = renderer!.root.findAll( - (node) => node.type === "output" && node.props?.["aria-live"] === "polite", - ); - expect(liveRegions.length).toBeGreaterThan(0); await act(async () => { renderer?.unmount(); }); }); - test("action toasts render a button and fire onClick", async () => { - (globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true; - - let clicked = 0; + test("error toasts are red and persistent", async () => { + let api: ReturnType | null = null; + let toastId = ""; let renderer: TestRenderer.ReactTestRenderer | null = null; await act(async () => { renderer = TestRenderer.create( - - { - clicked += 1; - }, - }, + <> + + { + api = value; }} /> - , - ); - }); - - const button = renderer!.root.findByType("button"); - expect(textFromNode(renderer!.toJSON())).toContain("Unarchive"); - - await act(async () => { - button.props.onClick(); - }); - - expect(clicked).toBe(1); - - await act(async () => { - renderer?.unmount(); - }); - }); - - test("error toasts do not auto-dismiss", async () => { - (globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true; - - let renderer: TestRenderer.ReactTestRenderer | null = null; - await act(async () => { - renderer = TestRenderer.create( - - - , + , ); }); await act(async () => { - await new Promise((resolve) => setTimeout(resolve, 20)); + toastId = api!.push({ message: "Conflict", tone: "error", autoDismissMs: 5 }); }); + await flushSonnerUpdates(); expect(textFromNode(renderer!.toJSON())).toContain("Conflict"); + expect( + sonnerToast.getToasts().find((toast) => toast.id === toastId), + ).toMatchObject({ + duration: Infinity, + title: "Conflict", + type: "error", + }); await act(async () => { renderer?.unmount(); }); }); - test("multiple toasts stack in insertion order", async () => { - (globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true; - + test("dismiss removes one toast from the Sonner store", async () => { let api: ReturnType | null = null; + let secondId = ""; let renderer: TestRenderer.ReactTestRenderer | null = null; - await act(async () => { renderer = TestRenderer.create( - - + + { api = value; }} /> - , + , ); }); await act(async () => { - api!.push({ message: "Second" }); + api!.push({ message: "First" }); + secondId = api!.push({ message: "Second" }); }); - - const toasts = renderer!.root.findAll( - (node) => node.props?.["data-toast-id"] != null, - ); - expect(toasts).toHaveLength(2); - expect(textFromInstance(toasts[0]!)).toContain("First"); - expect(textFromInstance(toasts[1]!)).toContain("Second"); + await flushSonnerUpdates(); await act(async () => { - renderer?.unmount(); - }); - }); - - test("dismiss removes a toast and leaves the rest reflowed", async () => { - (globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true; - - let api: ReturnType | null = null; - let firstId = ""; - let renderer: TestRenderer.ReactTestRenderer | null = null; - - await act(async () => { - renderer = TestRenderer.create( - - { - api = value; - }} - /> - , - ); - }); - - await act(async () => { - firstId = api!.push({ message: "Second" }); - }); - - await act(async () => { - api!.dismiss(firstId); + api!.dismiss(secondId); }); + await flushSonnerUpdates(); const text = textFromNode(renderer!.toJSON()); expect(text).toContain("First"); @@ -193,4 +161,58 @@ describe("ToastProvider", () => { renderer?.unmount(); }); }); + + test("clear removes all Sonner toasts", async () => { + let api: ReturnType | null = null; + let renderer: TestRenderer.ReactTestRenderer | null = null; + + await act(async () => { + renderer = TestRenderer.create( + + + { + api = value; + }} + /> + , + ); + }); + + await act(async () => { + api!.push({ message: "First" }); + api!.push({ message: "Second" }); + }); + await flushSonnerUpdates(); + + await act(async () => { + api!.clear(); + }); + await flushSonnerUpdates(); + + const text = textFromNode(renderer!.toJSON()); + expect(text).not.toContain("First"); + expect(text).not.toContain("Second"); + + await act(async () => { + renderer?.unmount(); + }); + }); + + test("ToastProvider is transparent for existing test wrappers", async () => { + let renderer: TestRenderer.ReactTestRenderer | null = null; + await act(async () => { + renderer = TestRenderer.create( + + wrapped child + , + ); + }); + + expect(textFromNode(renderer!.toJSON())).toContain("wrapped child"); + + await act(async () => { + renderer?.unmount(); + }); + }); }); diff --git a/apps/fabro-web/app/components/toast.tsx b/apps/fabro-web/app/components/toast.tsx index e17d194c0..f45c95ff4 100644 --- a/apps/fabro-web/app/components/toast.tsx +++ b/apps/fabro-web/app/components/toast.tsx @@ -1,164 +1,84 @@ -import { - createContext, - use, - useCallback, - useEffect, - useMemo, - useRef, - useState, - type ReactNode, -} from "react"; -import { XMarkIcon } from "@heroicons/react/20/solid"; +import type { ReactNode } from "react"; +import { toast as sonnerToast, useSonner } from "sonner"; export type ToastTone = "info" | "error"; -export interface ToastAction { - label: string; - onClick: () => void; -} - export interface ToastInput { message: string; tone?: ToastTone; - action?: ToastAction; autoDismissMs?: number; } -interface ToastRecord extends ToastInput { - id: string; - tone: ToastTone; -} - interface ToastContextValue { push: (toast: ToastInput) => string; dismiss: (id: string) => void; clear: () => void; } -const ToastContext = createContext(null); +let nextToastId = 0; -function toastClassName(tone: ToastTone): string { - return tone === "error" - ? "border-coral/40 bg-rose-950/90 text-rose-50" - : "border-line bg-panel/95 text-fg-2"; +function push(toast: ToastInput): string { + const id = `toast-${nextToastId++}`; + const options = { + id, + ...(toast.tone === "error" + ? { duration: Infinity } + : toast.autoDismissMs != null + ? { duration: toast.autoDismissMs } + : {}), + }; + + if (toast.tone === "error") { + sonnerToast.error(toast.message, options); + } else { + sonnerToast(toast.message, options); + } + return id; } -function ToastRoot({ - toasts, - onDismiss, -}: { - toasts: ToastRecord[]; - onDismiss: (id: string) => void; -}) { - if (toasts.length === 0) return null; +const toastApi: ToastContextValue = { + push, + dismiss: (id) => { + sonnerToast.dismiss(id); + }, + clear: () => { + sonnerToast.dismiss(); + }, +}; +/** + * No-op wrapper retained so existing test harnesses and the standalone terminal + * route can keep their mount points. In a browser the real + * is mounted globally in AppShell; in non-DOM test environments we + * render an aria-live fallback that subscribes to the Sonner store so test + * assertions can read the toast text. + */ +export function ToastProvider({ children }: { children: ReactNode }) { + if (typeof document !== "undefined") { + return <>{children}; + } return ( - - {toasts.map((toast) => ( -
-
-

{toast.message}

- {toast.tone === "error" && ( - - )} -
- {toast.action && ( -
- -
- )} -
- ))} -
- ); -} - -export function ToastProvider({ - children, - autoDismissMs = 3500, -}: { - children: ReactNode; - autoDismissMs?: number; -}) { - const [toasts, setToasts] = useState([]); - const nextIdRef = useRef(0); - const timeoutIdsRef = useRef(new Map>()); - - const dismiss = useCallback((id: string) => { - const timeoutId = timeoutIdsRef.current.get(id); - if (timeoutId) { - clearTimeout(timeoutId); - timeoutIdsRef.current.delete(id); - } - setToasts((current) => current.filter((toast) => toast.id !== id)); - }, []); - - const clear = useCallback(() => { - for (const timeoutId of timeoutIdsRef.current.values()) { - clearTimeout(timeoutId); - } - timeoutIdsRef.current.clear(); - setToasts([]); - }, []); - - const push = useCallback((toast: ToastInput) => { - const id = `toast-${nextIdRef.current++}`; - const record: ToastRecord = { - ...toast, - id, - tone: toast.tone ?? "info", - }; - - setToasts((current) => [...current, record]); - - if (record.tone !== "error") { - const timeoutId = setTimeout(() => dismiss(id), toast.autoDismissMs ?? autoDismissMs); - timeoutIdsRef.current.set(id, timeoutId); - } - - return id; - }, [autoDismissMs, dismiss]); - - useEffect(() => clear, [clear]); - - const value = useMemo(() => ({ push, dismiss, clear }), [push, dismiss, clear]); - - return ( - + <> {children} - - + + ); } export function useToast(): ToastContextValue { - const value = use(ToastContext); - if (!value) { - throw new Error("useToast must be used within a ToastProvider"); - } - return value; + return toastApi; +} + +function NonDomToastOutput() { + const { toasts } = useSonner(); + if (toasts.length === 0) return null; + return ( + + {toasts.map((toast) => ( +

+ {typeof toast.title === "function" ? toast.title() : toast.title} +

+ ))} +
+ ); } diff --git a/apps/fabro-web/app/components/ui.tsx b/apps/fabro-web/app/components/ui.tsx index fac25dcf4..8b3daa5de 100644 --- a/apps/fabro-web/app/components/ui.tsx +++ b/apps/fabro-web/app/components/ui.tsx @@ -2,15 +2,20 @@ // exposes the primary button, secondary button, input, error message, and // copy button so the auth and in-app surfaces can match. -import { useEffect, useId, useRef, useState, type ReactNode } from "react"; -import { createPortal } from "react-dom"; import { Dialog, DialogPanel, DialogTitle } from "@headlessui/react"; import { ClipboardDocumentCheckIcon, ClipboardIcon, } from "@heroicons/react/16/solid"; - -import { hoverCardStyle } from "./hover-card-style"; +import * as HoverCardPrimitive from "@radix-ui/react-hover-card"; +import * as TooltipPrimitive from "@radix-ui/react-tooltip"; +import { + createContext, + useContext, + useState, + type ComponentProps, + type ReactNode, +} from "react"; export const INPUT_CLASS = "block w-full rounded-lg bg-panel-alt px-3.5 py-2.5 text-base text-fg outline-1 -outline-offset-1 outline-white/10 placeholder:text-fg-muted focus:outline-2 focus:-outline-offset-1 focus:outline-teal-500 sm:text-sm"; @@ -133,49 +138,37 @@ export function ConfirmDialog({ ); } -// Shared hover/focus state for `Tooltip` and `HoverCard`. Returns the trigger -// props to spread and the trigger rect (only while open) for positioning. -// `openDelay` (ms) defers showing the card until the pointer has dwelled on -// the trigger long enough — protects against fetch-on-mount popovers being -// triggered by an incidental cursor sweep through a list. -function useHoverAnchor(openDelay = 0) { - const [open, setOpen] = useState(false); - const triggerRef = useRef(null); - const timerRef = useRef | null>(null); +const TOOLTIP_DELAY_DURATION = 200; +const TOOLTIP_SKIP_DELAY_DURATION = 300; - const clearTimer = () => { - if (timerRef.current !== null) { - clearTimeout(timerRef.current); - timerRef.current = null; - } - }; - const requestOpen = () => { - if (openDelay <= 0) { - setOpen(true); - return; - } - clearTimer(); - timerRef.current = setTimeout(() => { - timerRef.current = null; - setOpen(true); - }, openDelay); - }; - const requestClose = () => { - clearTimer(); - setOpen(false); - }; +const HasTooltipProviderContext = createContext(false); - useEffect(() => clearTimer, []); +type TooltipProviderProps = ComponentProps; - const rect = open ? (triggerRef.current?.getBoundingClientRect() ?? null) : null; - const triggerProps = { - ref: triggerRef, - onMouseEnter: requestOpen, - onMouseLeave: requestClose, - onFocus: requestOpen, - onBlur: requestClose, - }; - return { open, rect, triggerProps }; +function canUseOverlayDom() { + return typeof window !== "undefined" && typeof document !== "undefined"; +} + +export function TooltipProvider({ + delayDuration = TOOLTIP_DELAY_DURATION, + skipDelayDuration = TOOLTIP_SKIP_DELAY_DURATION, + children, + ...props +}: TooltipProviderProps) { + if (!canUseOverlayDom()) { + return <>{children}; + } + return ( + + + {children} + + + ); } export function Tooltip({ @@ -185,33 +178,34 @@ export function Tooltip({ label: ReactNode; children: ReactNode; }) { - const { open, rect, triggerProps } = useHoverAnchor(); - const id = useId(); - const portalTarget = typeof document === "undefined" ? null : document.body; + // Tests and isolated mounts may render without an ancestor + // ; Radix throws in that case, so supply a local provider. + const hasProvider = useContext(HasTooltipProviderContext); - return ( - <> - - {children} - - {rect && portalTarget - ? createPortal( - , - portalTarget, - ) - : null} - + if (!canUseOverlayDom()) { + return {children}; + } + + const root = ( + + + {children} + + + + {label} + + + ); + + return hasProvider ? root : {root}; } /** @@ -230,29 +224,26 @@ export function HoverCard({ className?: string; openDelay?: number; }) { - const { open, rect, triggerProps } = useHoverAnchor(openDelay); - const id = useId(); - const portalTarget = typeof document === "undefined" ? null : document.body; - + if (!canUseOverlayDom()) { + return {children}; + } return ( - <> - - {children} - - {rect && portalTarget - ? createPortal( - , - portalTarget, - ) - : null} - + + + {children} + + + + {content} + + + ); } diff --git a/apps/fabro-web/app/layouts/app-shell.tsx b/apps/fabro-web/app/layouts/app-shell.tsx index 32c222664..ca0cbe417 100644 --- a/apps/fabro-web/app/layouts/app-shell.tsx +++ b/apps/fabro-web/app/layouts/app-shell.tsx @@ -12,8 +12,9 @@ import { XMarkIcon, } from "@heroicons/react/24/outline"; import { Link, Outlet, useLocation, useMatches } from "react-router"; +import { Toaster } from "sonner"; import { ErrorState } from "../components/state"; -import { ToastProvider } from "../components/toast"; +import { TooltipProvider } from "../components/ui"; import { AskFabroLayoutProvider, useAskFabroLayout } from "../lib/ask-fabro-layout"; import { DemoModeProvider } from "../lib/demo-mode"; import { useAuthMe } from "../lib/queries"; @@ -63,7 +64,7 @@ export default function AppShell() { return ( - +
+ {typeof document !== "undefined" && ( + + )}
-
+
); } diff --git a/apps/fabro-web/app/routes/run-detail.test.ts b/apps/fabro-web/app/routes/run-detail.test.ts index 6d0bd0427..e30d38da8 100644 --- a/apps/fabro-web/app/routes/run-detail.test.ts +++ b/apps/fabro-web/app/routes/run-detail.test.ts @@ -437,7 +437,7 @@ describe("actionMenuSeparatorVisibility", () => { }); describe("handleLifecycleToastResult", () => { - type PushedToast = { message: string; action?: { label: string; onClick: () => void } }; + type PushedToast = { message: string }; function makeToastApi() { const pushed: PushedToast[] = []; diff --git a/apps/fabro-web/app/routes/run-overview.tsx b/apps/fabro-web/app/routes/run-overview.tsx index 131c11738..688f2ddcf 100644 --- a/apps/fabro-web/app/routes/run-overview.tsx +++ b/apps/fabro-web/app/routes/run-overview.tsx @@ -1,13 +1,12 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react"; -import { createPortal } from "react-dom"; import { useNavigate, useParams } from "react-router"; import { graphTheme } from "../lib/graph-theme"; import { ApiError } from "../lib/api-client"; import { useRun, useRunGraph, useRunStages } from "../lib/queries"; +import { FloatingTooltip } from "../components/floating-tooltip"; import { RunSummaryPanel } from "../components/run-summary-panel"; import { StagePopover } from "../components/stage-popover"; import { StageSidebar } from "../components/stage-sidebar"; -import { hoverCardStyle } from "../components/hover-card-style"; import { GRAPH_DEFAULT_ZOOM_INDEX, GRAPH_ZOOM_STEPS, @@ -300,22 +299,19 @@ export default function RunOverview() { /> )} - {hoveredNode && - typeof document !== "undefined" && - createPortal( -
- -
, - document.body, - )} + {hoveredNode && ( + + + + )} ); } diff --git a/apps/fabro-web/app/routes/run-terminal.tsx b/apps/fabro-web/app/routes/run-terminal.tsx index 78c7f42cf..0f7d66af9 100644 --- a/apps/fabro-web/app/routes/run-terminal.tsx +++ b/apps/fabro-web/app/routes/run-terminal.tsx @@ -1,4 +1,5 @@ import { useEffect } from "react"; +import { Toaster } from "sonner"; import TerminalView from "../components/terminal-view"; import { ToastProvider } from "../components/toast"; @@ -17,6 +18,9 @@ export default function RunTerminal({ params }: { params: { id: string } }) {
+ {typeof document !== "undefined" && ( + + )}
); } diff --git a/apps/fabro-web/app/routes/settings-models.tsx b/apps/fabro-web/app/routes/settings-models.tsx index 6a3867275..936f77da0 100644 --- a/apps/fabro-web/app/routes/settings-models.tsx +++ b/apps/fabro-web/app/routes/settings-models.tsx @@ -26,6 +26,7 @@ import { SortHeader, type SortDirection, } from "../components/runs-list/sort-header"; +import { Tooltip } from "../components/ui"; import { formatContextWindow, formatTokensPerSecond } from "../lib/format"; export function meta() { @@ -512,7 +513,13 @@ function TestStatusCell({ state }: { state: RowState | undefined }) { ); } return ( - + + {state.message} + + } + > - - {state.message} - - + ); } function ModelNameCell({ model }: { model: Model }) { const hasAliases = model.aliases.length > 0; const nameNode = hasAliases ? ( - + + + Aliases + + {model.aliases.map((alias) => ( + + {alias} + + ))} + + } + > - - - Aliases - - {model.aliases.map((alias) => ( - - {alias} - - ))} - - + ) : ( {model.id} ); diff --git a/apps/fabro-web/package.json b/apps/fabro-web/package.json index 6dbad2669..ed95f5298 100644 --- a/apps/fabro-web/package.json +++ b/apps/fabro-web/package.json @@ -21,6 +21,8 @@ "@pierre/theme": "0.0.28", "@pierre/trees": "1.0.0-beta.3", "@qltysh/fabro-api-client": "workspace:*", + "@radix-ui/react-hover-card": "^1.1.15", + "@radix-ui/react-tooltip": "^1.2.8", "@tailwindcss/typography": "^0.5.19", "@viz-js/viz": "^3.24.0", "@xterm/addon-fit": "^0.11.0", @@ -31,6 +33,7 @@ "react-dom": "^19.2.4", "react-router": "7.12.0", "remark-gfm": "^4.0.1", + "sonner": "^2.0.7", "swr": "^2.4.1" }, "devDependencies": { diff --git a/bun.lock b/bun.lock index e78ff176e..c0fb7a68a 100644 --- a/bun.lock +++ b/bun.lock @@ -18,6 +18,8 @@ "@pierre/theme": "0.0.28", "@pierre/trees": "1.0.0-beta.3", "@qltysh/fabro-api-client": "workspace:*", + "@radix-ui/react-hover-card": "^1.1.15", + "@radix-ui/react-tooltip": "^1.2.8", "@tailwindcss/typography": "^0.5.19", "@viz-js/viz": "^3.24.0", "@xterm/addon-fit": "^0.11.0", @@ -28,6 +30,7 @@ "react-dom": "^19.2.4", "react-router": "7.12.0", "remark-gfm": "^4.0.1", + "sonner": "^2.0.7", "swr": "^2.4.1", }, "devDependencies": { @@ -452,7 +455,7 @@ "@radix-ui/react-slider": ["@radix-ui/react-slider@1.3.6", "", { "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-use-size": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-JPYb1GuM1bxfjMRlNLE+BcmBC8onfCi60Blk7OBqi2MLTFdS+8401U4uFjnwkOr49BLmXxLC6JHkvAsx5OJvHw=="], - "@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.4", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA=="], + "@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], "@radix-ui/react-switch": ["@radix-ui/react-switch@1.2.6", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-use-size": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ=="], @@ -1630,6 +1633,8 @@ "socks-proxy-agent": ["socks-proxy-agent@8.0.5", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "socks": "^2.8.3" } }, "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw=="], + "sonner": ["sonner@2.0.7", "", { "peerDependencies": { "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w=="], + "source-map": ["source-map@0.7.3", "", {}, "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ=="], "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], @@ -1850,8 +1855,6 @@ "@radix-ui/react-alert-dialog/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], - "@radix-ui/react-alert-dialog/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - "@radix-ui/react-arrow/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], "@radix-ui/react-aspect-ratio/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], @@ -1868,8 +1871,6 @@ "@radix-ui/react-collection/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], - "@radix-ui/react-collection/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - "@radix-ui/react-context-menu/@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="], "@radix-ui/react-context-menu/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], @@ -1878,8 +1879,6 @@ "@radix-ui/react-dialog/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], - "@radix-ui/react-dialog/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - "@radix-ui/react-dismissable-layer/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], "@radix-ui/react-dropdown-menu/@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="], @@ -1902,8 +1901,6 @@ "@radix-ui/react-menu/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], - "@radix-ui/react-menu/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - "@radix-ui/react-menubar/@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="], "@radix-ui/react-menubar/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], @@ -1924,14 +1921,14 @@ "@radix-ui/react-popover/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], - "@radix-ui/react-popover/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - "@radix-ui/react-popper/@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="], "@radix-ui/react-popper/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], "@radix-ui/react-portal/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], + "@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.4", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA=="], + "@radix-ui/react-progress/@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="], "@radix-ui/react-progress/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], @@ -1952,8 +1949,6 @@ "@radix-ui/react-select/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], - "@radix-ui/react-select/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - "@radix-ui/react-separator/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], "@radix-ui/react-slider/@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="], @@ -1986,8 +1981,6 @@ "@radix-ui/react-tooltip/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], - "@radix-ui/react-tooltip/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - "@radix-ui/react-visually-hidden/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], "@remotion/bundler/esbuild": ["esbuild@0.25.0", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.0", "@esbuild/android-arm": "0.25.0", "@esbuild/android-arm64": "0.25.0", "@esbuild/android-x64": "0.25.0", "@esbuild/darwin-arm64": "0.25.0", "@esbuild/darwin-x64": "0.25.0", "@esbuild/freebsd-arm64": "0.25.0", "@esbuild/freebsd-x64": "0.25.0", "@esbuild/linux-arm": "0.25.0", "@esbuild/linux-arm64": "0.25.0", "@esbuild/linux-ia32": "0.25.0", "@esbuild/linux-loong64": "0.25.0", "@esbuild/linux-mips64el": "0.25.0", "@esbuild/linux-ppc64": "0.25.0", "@esbuild/linux-riscv64": "0.25.0", "@esbuild/linux-s390x": "0.25.0", "@esbuild/linux-x64": "0.25.0", "@esbuild/netbsd-arm64": "0.25.0", "@esbuild/netbsd-x64": "0.25.0", "@esbuild/openbsd-arm64": "0.25.0", "@esbuild/openbsd-x64": "0.25.0", "@esbuild/sunos-x64": "0.25.0", "@esbuild/win32-arm64": "0.25.0", "@esbuild/win32-ia32": "0.25.0", "@esbuild/win32-x64": "0.25.0" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw=="], @@ -2086,8 +2079,6 @@ "radix-ui/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], - "radix-ui/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - "recast/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], "source-map-support/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], @@ -2120,68 +2111,6 @@ "@babel/helper-compilation-targets/lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], - "@radix-ui/react-accordion/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - - "@radix-ui/react-arrow/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - - "@radix-ui/react-aspect-ratio/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - - "@radix-ui/react-checkbox/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - - "@radix-ui/react-collapsible/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - - "@radix-ui/react-context-menu/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - - "@radix-ui/react-dismissable-layer/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - - "@radix-ui/react-dropdown-menu/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - - "@radix-ui/react-focus-scope/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - - "@radix-ui/react-form/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - - "@radix-ui/react-hover-card/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - - "@radix-ui/react-label/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - - "@radix-ui/react-menubar/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - - "@radix-ui/react-navigation-menu/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - - "@radix-ui/react-one-time-password-field/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - - "@radix-ui/react-password-toggle-field/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - - "@radix-ui/react-popper/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - - "@radix-ui/react-portal/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - - "@radix-ui/react-progress/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - - "@radix-ui/react-radio-group/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - - "@radix-ui/react-roving-focus/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - - "@radix-ui/react-scroll-area/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - - "@radix-ui/react-separator/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - - "@radix-ui/react-slider/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - - "@radix-ui/react-switch/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - - "@radix-ui/react-tabs/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - - "@radix-ui/react-toast/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - - "@radix-ui/react-toggle-group/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - - "@radix-ui/react-toggle/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - - "@radix-ui/react-toolbar/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - - "@radix-ui/react-visually-hidden/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - "@remotion/bundler/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.0", "", { "os": "aix", "cpu": "ppc64" }, "sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ=="], "@remotion/bundler/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.25.0", "", { "os": "android", "cpu": "arm" }, "sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g=="],