From 5231381717cfb3f3cf674fed86b23737361b0b26 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Wed, 4 Sep 2024 17:55:36 +0100 Subject: [PATCH] ElectricSQL run page and react-window-splitter (#1277) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * WIP on using react-window-splitter * WIP with new resizable panels and SSR * Use the cookie package * Resizable storybook page * Increase indexing memory limit * Fixed v2 usage meter displaying when on paid plan (#1255) * Fixed v2 usage meter displaying when on paid plan * Show the free usage panel only for v3 projects * Concurrency page and more accurate tracking (#1252) * Initial TaskRunConcurrencyTracker implementation * MARQS calls a subscriber to events * When enqueuing add the extra required metadata * Track concurrency per environment for tasks too * Admin page for global concurrency * Use the new concurrency tracker on the tasks page * Useful performance test task * getAllTaskIdentifiers() * New page for concurrency * BackgroundWorkerTask index for quick lookup of task identifiers * Added a way to get concurrency for environments * Added upgrade/request more concurrency button * Queued task column working * Use defer and suspense * Added queue column to the concurrency environments table * Some comments added for clarity * Fixed bad log message * Sidemenu: move lower and rename to “Concurrency limits” * Only show the environments, not tasks. Renamed to “Concurrency limits” * v3: fix unfreezable state crashes for runs with multiple waits (#1253) * support named capture groups * write crash errors to attempt.error * make restored pod names unique per checkpoint * use last eight characters of checkpoint id instead * add more chaos monkey env vars * Ignore unfreezable states * prevent excessive queue config parsing errors * handle dependency resume edge case * better entry point logging * ignore checkpoint cancellation timeouts * add missing idempotency keys to wait for dep replays * remove checkpoints between attempts * fix retry container names on kubernetes * add changeset * fix types * bring back internal duration timers * Added more logging to TaskRunConcurrencyTracker and some more try/catches * Call subscriber.messageDequeued in dequeueMessageInSharedQueue * Added messageReplaced to concurrency tracking (when freezing) * Added depenenciesToBundle guide to bundle all packages * Include the old message data when replacing, so we get the projectId etc. * Fix restored container names * Fix for schedule page not scrolling * Added a description panel to the Concurrency admin page * chore: Update version for release (beta) (#1256) Co-authored-by: github-actions[bot] * Release 3.0.0-beta.53 * Added a note to use batchTrigger() instead of trigger() * The latest react-window-splitter fixes the ESM issues * Set sensible defaults for the run page * Deployments page * Test page * Schedules page * Latest version of react-window-splitter (0.2.5) * Updated to the latest version: react-window-splitter * Callout if runs don’t start right away now has some top margin * Small padding fix * styled the handle focus state * Added isStaticAtRest prop to resizable panel * Updated resizable storybook * Inline code blocks behave nicer when text wraps * Added ElectricSQL to docker-compose, available on 3060 * Extracted some logic out of the eventRepository for getting a trace. This will be used on the frontend * Use the new util * More restructuring ready to use the trace summary from the frontend * Using ElectricSQL for the run page data * Min size for resizable panel on test page * Don’t load the trace in the RunPresenter anymore * Fix for the resizable panels on the run page * Added overflow hidden to the panel group * min size for the test page left hand panel * Updated to latest window-splitter version * Removed unused const * One fix for client-server mismatch * Slight improvement in the loading state * Restructured the page so the loading is better * Improvement to the loading states * Improved the loading behaviour with the inspector * WIP on auth, having problems with it * Upgrade Remix to 2.9.1 (same as PR #1096) * Switched structure around again so we only call the useTrace hook from the client * Added auth to the sync * Overscan more rows in the tree view * Fix for TS error * Remove duplicate import * Revert "Upgrade Remix to 2.9.1 (same as PR #1096)" This reverts commit e63ee9e924d2d532136f9fd6fcd3b358717637be. * save cookie only when id is used * Deployment table now scrolls * removed imports * A lot of changes to make the inspector live too… WIP * More major overhauls to get the synced version of the run page working… * If a span is completed show that * Set the debounce much lower for selecting the span view * Load the details run inspector data on demand * Delete the SpanPresenter * Use the async payload because it deals with superjson * Fixed weird merge conflict * Share some inspector timeline components * A couple of layout tweaks * Improved the run inspector loading states * Fix for paragaph errors * Fix for focusing on a span * Undefined typre for useSyncedShape * ELECTRIC_ORIGIN env var doesn’t have a default, added to the examples * Updated @electric-sql/react package to the latest * Fix the timeline duration stretching * Added some better error handling for the electric sync * More logging * Better error when there are bad responses * Turn off resizable snapshots, there’s a bug --------- Co-authored-by: nicktrn <55853254+nicktrn@users.noreply.github.com> Co-authored-by: James Ritchie Co-authored-by: James Ritchie Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] --- .env.example | 1 + apps/webapp/app/components/code/CodeBlock.tsx | 4 +- .../webapp/app/components/code/InlineCode.tsx | 2 +- .../app/components/primitives/Resizable.tsx | 31 +- .../primitives/TreeView/TreeView.tsx | 2 +- .../components/runs/v3/InspectorTimeline.tsx | 63 + .../app/components/runs/v3/RunInspector.tsx | 650 ++++++++ .../app/components/runs/v3/SpanInspector.tsx | 313 ++++ .../app/components/runs/v3/SpanTitle.tsx | 13 +- apps/webapp/app/env.server.ts | 1 + apps/webapp/app/hooks/useSyncRunPage.ts | 16 + apps/webapp/app/hooks/useSyncTrace.ts | 19 + apps/webapp/app/hooks/useSyncTraceRuns.ts | 18 + apps/webapp/app/hooks/useSyncedShape.ts | 71 + .../presenters/v3/RunListPresenter.server.ts | 2 +- .../app/presenters/v3/RunPresenter.server.ts | 78 +- .../app/presenters/v3/SpanPresenter.server.ts | 304 ---- .../route.tsx | 200 +-- .../route.tsx | 507 +++--- .../route.tsx | 8 +- .../route.tsx | 16 +- .../route.tsx | 14 +- .../route.tsx | 986 ----------- .../resources.runs.$runParam.logs.download.ts | 8 +- .../app/routes/resources.runs.$runParam.ts | 240 +++ .../app/routes/storybook.resizable/route.tsx | 24 + apps/webapp/app/routes/storybook/route.tsx | 4 + .../webapp/app/routes/sync.traces.$traceId.ts | 69 + .../app/routes/sync.traces.runs.$traceId.ts | 79 + .../app/services/resizablePanel.server.ts | 39 + apps/webapp/app/services/resizablePanel.ts | 31 - apps/webapp/app/utils/longPollingFetch.ts | 46 + apps/webapp/app/utils/taskEvent.ts | 570 +++++++ apps/webapp/app/v3/eventRepository.server.ts | 594 +------ apps/webapp/package.json | 6 +- docker/docker-compose.yml | 17 + pnpm-lock.yaml | 1477 ++++++++++++++++- 37 files changed, 4172 insertions(+), 2351 deletions(-) create mode 100644 apps/webapp/app/components/runs/v3/InspectorTimeline.tsx create mode 100644 apps/webapp/app/components/runs/v3/RunInspector.tsx create mode 100644 apps/webapp/app/components/runs/v3/SpanInspector.tsx create mode 100644 apps/webapp/app/hooks/useSyncRunPage.ts create mode 100644 apps/webapp/app/hooks/useSyncTrace.ts create mode 100644 apps/webapp/app/hooks/useSyncTraceRuns.ts create mode 100644 apps/webapp/app/hooks/useSyncedShape.ts delete mode 100644 apps/webapp/app/presenters/v3/SpanPresenter.server.ts delete mode 100644 apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.v3.$projectParam.runs.$runParam.spans.$spanParam/route.tsx create mode 100644 apps/webapp/app/routes/resources.runs.$runParam.ts create mode 100644 apps/webapp/app/routes/storybook.resizable/route.tsx create mode 100644 apps/webapp/app/routes/sync.traces.$traceId.ts create mode 100644 apps/webapp/app/routes/sync.traces.runs.$traceId.ts create mode 100644 apps/webapp/app/services/resizablePanel.server.ts delete mode 100644 apps/webapp/app/services/resizablePanel.ts create mode 100644 apps/webapp/app/utils/longPollingFetch.ts create mode 100644 apps/webapp/app/utils/taskEvent.ts diff --git a/.env.example b/.env.example index f38004ef4..2e1725cf0 100644 --- a/.env.example +++ b/.env.example @@ -10,6 +10,7 @@ DIRECT_URL=${DATABASE_URL} REMIX_APP_PORT=3030 APP_ENV=development APP_ORIGIN=http://localhost:3030 +ELECTRIC_ORIGIN=http://localhost:3060 NODE_ENV=development V3_ENABLED=true diff --git a/apps/webapp/app/components/code/CodeBlock.tsx b/apps/webapp/app/components/code/CodeBlock.tsx index 1d0925a53..62a476024 100644 --- a/apps/webapp/app/components/code/CodeBlock.tsx +++ b/apps/webapp/app/components/code/CodeBlock.tsx @@ -1,7 +1,7 @@ import { Clipboard, ClipboardCheck } from "lucide-react"; import type { Language, PrismTheme } from "prism-react-renderer"; import { Highlight, Prism } from "prism-react-renderer"; -import { forwardRef, useCallback, useState } from "react"; +import { forwardRef, ReactNode, useCallback, useState } from "react"; import { cn } from "~/utils/cn"; import { Paragraph } from "../primitives/Paragraph"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../primitives/Tooltip"; @@ -385,7 +385,7 @@ function Chrome({ title }: { title?: string }) { ); } -export function TitleRow({ title }: { title: string }) { +export function TitleRow({ title }: { title: ReactNode }) { return (
diff --git a/apps/webapp/app/components/code/InlineCode.tsx b/apps/webapp/app/components/code/InlineCode.tsx index 8ff5b9916..120e86425 100644 --- a/apps/webapp/app/components/code/InlineCode.tsx +++ b/apps/webapp/app/components/code/InlineCode.tsx @@ -1,7 +1,7 @@ import { cn } from "~/utils/cn"; const inlineCode = - "px-1 py-0.5 rounded border border-charcoal-700 bg-charcoal-800 text-charcoal-200 font-mono"; + "px-1 py-0.5 rounded border border-charcoal-700 bg-charcoal-800 text-charcoal-200 font-mono text-wrap"; const variants = { "extra-extra-small": "text-xxs", diff --git a/apps/webapp/app/components/primitives/Resizable.tsx b/apps/webapp/app/components/primitives/Resizable.tsx index 776b210f3..ac4cba25c 100644 --- a/apps/webapp/app/components/primitives/Resizable.tsx +++ b/apps/webapp/app/components/primitives/Resizable.tsx @@ -1,32 +1,35 @@ "use client"; -import * as ResizablePrimitive from "react-resizable-panels"; +import React from "react"; +import { PanelGroup, Panel, PanelResizer } from "react-window-splitter"; import { cn } from "~/utils/cn"; -const ResizablePanelGroup = ({ - className, - ...props -}: React.ComponentProps) => ( - ) => ( + ); -const ResizablePanel = ResizablePrimitive.Panel; +const ResizablePanel = Panel; const ResizableHandle = ({ - withHandle, + withHandle = true, className, ...props -}: React.ComponentProps & { +}: React.ComponentProps & { withHandle?: boolean; }) => ( - div]:rotate-90", + "focus-visible:ring-ring group relative flex w-0.75 items-center justify-center after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:-translate-y-1/2 data-[panel-group-direction=vertical]:after:translate-x-0 hover:w-0.75 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-secondary/50 focus-visible:ring-offset-0 [&[data-panel-group-direction=vertical]>div]:rotate-90", className )} + size="3px" {...props} >
@@ -37,7 +40,9 @@ const ResizableHandle = ({ ))}
)} -
+ ); export { ResizableHandle, ResizablePanel, ResizablePanelGroup }; + +export type ResizableSnapshot = React.ComponentProps["snapshot"]; diff --git a/apps/webapp/app/components/primitives/TreeView/TreeView.tsx b/apps/webapp/app/components/primitives/TreeView/TreeView.tsx index dd19b42a1..e6d48b8cc 100644 --- a/apps/webapp/app/components/primitives/TreeView/TreeView.tsx +++ b/apps/webapp/app/components/primitives/TreeView/TreeView.tsx @@ -241,7 +241,7 @@ export function useTree({ index, }); }, - overscan: 20, + overscan: 50, }); const scrollToNodeFn = useCallback( diff --git a/apps/webapp/app/components/runs/v3/InspectorTimeline.tsx b/apps/webapp/app/components/runs/v3/InspectorTimeline.tsx new file mode 100644 index 000000000..1d574ff6a --- /dev/null +++ b/apps/webapp/app/components/runs/v3/InspectorTimeline.tsx @@ -0,0 +1,63 @@ +import { ReactNode } from "react"; +import { cn } from "~/utils/cn"; + +type RunTimelineItemProps = { + title: ReactNode; + subtitle?: ReactNode; + state: "complete" | "error"; +}; + +export function RunTimelineEvent({ title, subtitle, state }: RunTimelineItemProps) { + return ( +
+
+
+
+
+ {title} + {subtitle ? {subtitle} : null} +
+
+ ); +} + +type RunTimelineLineProps = { + title: ReactNode; + state: "complete" | "delayed" | "inprogress"; +}; + +export function RunTimelineLine({ title, state }: RunTimelineLineProps) { + return ( +
+
+
+
+
+ {title} +
+
+ ); +} diff --git a/apps/webapp/app/components/runs/v3/RunInspector.tsx b/apps/webapp/app/components/runs/v3/RunInspector.tsx new file mode 100644 index 000000000..62508edaf --- /dev/null +++ b/apps/webapp/app/components/runs/v3/RunInspector.tsx @@ -0,0 +1,650 @@ +import { CheckIcon, ClockIcon, CloudArrowDownIcon, QueueListIcon } from "@heroicons/react/20/solid"; +import { Link } from "@remix-run/react"; +import { formatDuration, formatDurationMilliseconds, TaskRunError } from "@trigger.dev/core/v3"; +import { useEffect } from "react"; +import { useTypedFetcher } from "remix-typedjson"; +import { ExitIcon } from "~/assets/icons/ExitIcon"; +import { CodeBlock, TitleRow } from "~/components/code/CodeBlock"; +import { EnvironmentLabel } from "~/components/environments/EnvironmentLabel"; +import { Button, LinkButton } from "~/components/primitives/Buttons"; +import { Callout } from "~/components/primitives/Callout"; +import { DateTime, DateTimeAccurate } from "~/components/primitives/DateTime"; +import { Header2, Header3 } from "~/components/primitives/Headers"; +import { Paragraph } from "~/components/primitives/Paragraph"; +import * as Property from "~/components/primitives/PropertyTable"; +import { Spinner } from "~/components/primitives/Spinner"; +import { TabButton, TabContainer } from "~/components/primitives/Tabs"; +import { TextLink } from "~/components/primitives/TextLink"; +import { InfoIconTooltip, SimpleTooltip } from "~/components/primitives/Tooltip"; +import { LiveTimer } from "~/components/runs/v3/LiveTimer"; +import { RunIcon } from "~/components/runs/v3/RunIcon"; +import { useOrganization } from "~/hooks/useOrganizations"; +import { useProject } from "~/hooks/useProject"; +import { useSearchParams } from "~/hooks/useSearchParam"; +import { RawRun } from "~/hooks/useSyncTraceRuns"; +import { loader } from "~/routes/resources.runs.$runParam"; +import { cn } from "~/utils/cn"; +import { formatCurrencyAccurate } from "~/utils/numberFormatter"; +import { + v3RunDownloadLogsPath, + v3RunPath, + v3RunSpanPath, + v3RunsPath, + v3SchedulePath, + v3TraceSpanPath, +} from "~/utils/pathBuilder"; +import { TraceSpan } from "~/utils/taskEvent"; +import { SpanLink } from "~/v3/eventRepository.server"; +import { isFinalRunStatus } from "~/v3/taskStatus"; +import { RunTimelineEvent, RunTimelineLine } from "./InspectorTimeline"; +import { RunTag } from "./RunTag"; +import { TaskRunStatusCombo } from "./TaskRunStatus"; + +/** + * The RunInspector displays live information about a run. + * Most of that data comes in as params but for some we need to fetch it. + */ +export function RunInspector({ + run, + span, + runParam, + closePanel, +}: { + run?: RawRun; + span?: TraceSpan; + runParam: string; + closePanel?: () => void; +}) { + const organization = useOrganization(); + const project = useProject(); + const { value, replace } = useSearchParams(); + const tab = value("tab"); + + const fetcher = useTypedFetcher(); + + useEffect(() => { + if (run?.friendlyId === undefined) return; + fetcher.load(`/resources/runs/${run.friendlyId}`); + }, [run?.friendlyId, run?.updatedAt]); + + if (!run) { + return ( +
+
+
+ + + + +
+ {closePanel && ( +
+
+
+ ); + } + + const environment = project.environments.find((e) => e.id === run.runtimeEnvironmentId); + const clientRunData = fetcher.state === "idle" ? fetcher.data : undefined; + + return ( +
+
+
+ + + {run.taskIdentifier} + +
+ {closePanel && ( +
+
+ + { + replace({ tab: "overview" }); + }} + shortcut={{ key: "o" }} + > + Overview + + { + replace({ tab: "detail" }); + }} + shortcut={{ key: "d" }} + > + Detail + + { + replace({ tab: "context" }); + }} + shortcut={{ key: "c" }} + > + Context + + +
+
+
+ {tab === "detail" ? ( +
+ + + Status + + {run ? : } + + + + Task + + + {run.taskIdentifier} + + } + content={`Filter runs by ${run.taskIdentifier}`} + /> + + + + Version + + {clientRunData ? ( + clientRunData?.version ? ( + clientRunData.version + ) : ( + + Never started + + + ) + ) : ( + + )} + + + + SDK version + + {clientRunData ? ( + clientRunData?.sdkVersion ? ( + clientRunData.sdkVersion + ) : ( + + Never started + + + ) + ) : ( + + )} + + + + Test run + + {run.isTest ? : "–"} + + + {environment && ( + + Environment + + + + + )} + + + Schedule + + {clientRunData ? ( + clientRunData.schedule ? ( +
+
+ + {clientRunData.schedule.generatorExpression} + + ({clientRunData.schedule.timezone}) +
+ + {clientRunData.schedule.description} + + } + content={`Go to schedule ${clientRunData.schedule.friendlyId}`} + /> +
+ ) : ( + "No schedule" + ) + ) : ( + + )} +
+
+ + Queue + + {clientRunData ? ( + <> +
Name: {clientRunData.queue.name}
+
+ Concurrency key:{" "} + {clientRunData.queue.concurrencyKey + ? clientRunData.queue.concurrencyKey + : "–"} +
+ + ) : ( + + )} +
+
+ + Time to live (TTL) + {run.ttl ?? "–"} + + + Tags + + {clientRunData ? ( + clientRunData.tags.length === 0 ? ( + "–" + ) : ( +
+ {clientRunData.tags.map((tag) => ( + + + + } + content={`Filter runs by ${tag}`} + /> + ))} +
+ ) + ) : ( + + )} +
+
+ {span?.links && span.links.length > 0 && ( + + Links + +
+ {span.links.map((link, index) => ( + + ))} +
+
+
+ )} + + Run invocation cost + + {run.baseCostInCents > 0 + ? formatCurrencyAccurate(run.baseCostInCents / 100) + : "–"} + + + + Compute cost + + {run.costInCents > 0 ? formatCurrencyAccurate(run.costInCents / 100) : "–"} + + + + Total cost + + {run.costInCents > 0 + ? formatCurrencyAccurate((run.baseCostInCents + run.costInCents) / 100) + : "–"} + + + + Usage duration + + {run.usageDurationMs > 0 + ? formatDurationMilliseconds(run.usageDurationMs, { style: "short" }) + : "–"} + + +
+
+ ) : tab === "context" ? ( +
+ {clientRunData ? ( + + ) : ( +
+ + Context loading… + + + } + /> +
+ )} +
+ ) : ( +
+
+ +
+ + <> + {clientRunData ? ( + <> + {clientRunData.payload !== undefined && ( + + )} + {clientRunData.error !== undefined ? ( + + ) : clientRunData.output !== undefined ? ( + + ) : null} + + ) : ( +
+ + Payload loading… + + + } + /> +
+ )} + +
+ )} +
+
+
+
+ {run.friendlyId !== runParam && ( + + Focus on run + + )} +
+
+ {run.logsDeletedAt === null ? ( + + Download logs + + ) : null} +
+
+
+ ); +} + +function PropertyLoading() { + return ; +} + +function RunTimeline({ run }: { run: RawRun }) { + const createdAt = new Date(run.createdAt); + const startedAt = run.startedAt ? new Date(run.startedAt) : null; + const delayUntil = run.delayUntil ? new Date(run.delayUntil) : null; + const expiredAt = run.expiredAt ? new Date(run.expiredAt) : null; + const updatedAt = new Date(run.updatedAt); + + const isFinished = isFinalRunStatus(run.status); + + return ( +
+ } + state="complete" + /> + {delayUntil && !expiredAt ? ( + {formatDuration(createdAt, delayUntil)} delay + ) : ( + + + + Delayed until {run.ttl && <>(TTL {run.ttl})} + + + ) + } + state={run.startedAt ? "complete" : "delayed"} + /> + ) : startedAt ? ( + + ) : ( + + {" "} + {run.ttl && <>(TTL {run.ttl})} + + } + state={run.startedAt || run.expiredAt ? "complete" : "inprogress"} + /> + )} + {expiredAt ? ( + } + state="error" + /> + ) : startedAt ? ( + <> + } + state="complete" + /> + {isFinished ? ( + <> + + } + state="complete" + /> + + ) : ( + + + + + + + } + state={"inprogress"} + /> + )} + + ) : null} +
+ ); +} + +function RunError({ error }: { error: TaskRunError }) { + switch (error.type) { + case "STRING_ERROR": + case "CUSTOM_ERROR": { + return ( +
+ +
+ ); + } + case "BUILT_IN_ERROR": + case "INTERNAL_ERROR": { + const name = "name" in error ? error.name : error.code; + return ( +
+ {name} + {error.message && {error.message}} + {error.stackTrace && ( + + )} +
+ ); + } + } +} + +function PacketDisplay({ + data, + dataType, + title, +}: { + data: string; + dataType: string; + title: string; +}) { + switch (dataType) { + case "application/store": { + return ( +
+ + {title} + + + Download + +
+ ); + } + case "text/plain": { + return ( + + ); + } + default: { + return ( + + ); + } + } +} + +function SpanLinkElement({ link }: { link: SpanLink }) { + const organization = useOrganization(); + const project = useProject(); + + switch (link.type) { + case "run": { + return ( + + {link.title} + + ); + } + case "span": { + return ( + + {link.title} + + ); + } + } + + return null; +} diff --git a/apps/webapp/app/components/runs/v3/SpanInspector.tsx b/apps/webapp/app/components/runs/v3/SpanInspector.tsx new file mode 100644 index 000000000..4b6ac7979 --- /dev/null +++ b/apps/webapp/app/components/runs/v3/SpanInspector.tsx @@ -0,0 +1,313 @@ +import { ExitIcon } from "~/assets/icons/ExitIcon"; +import { CodeBlock } from "~/components/code/CodeBlock"; +import { Button } from "~/components/primitives/Buttons"; +import { DateTimeAccurate } from "~/components/primitives/DateTime"; +import { Header2 } from "~/components/primitives/Headers"; +import * as Property from "~/components/primitives/PropertyTable"; +import { TabButton, TabContainer } from "~/components/primitives/Tabs"; +import { TextLink } from "~/components/primitives/TextLink"; +import { InfoIconTooltip, SimpleTooltip } from "~/components/primitives/Tooltip"; +import { RunIcon } from "~/components/runs/v3/RunIcon"; +import { SpanEvents } from "~/components/runs/v3/SpanEvents"; +import { SpanTitle } from "~/components/runs/v3/SpanTitle"; +import { TaskRunAttemptStatusCombo } from "~/components/runs/v3/TaskRunAttemptStatus"; +import { useOrganization } from "~/hooks/useOrganizations"; +import { useProject } from "~/hooks/useProject"; +import { useSearchParams } from "~/hooks/useSearchParam"; +import { cn } from "~/utils/cn"; +import { v3RunPath, v3RunsPath, v3TraceSpanPath } from "~/utils/pathBuilder"; +import { TraceSpan } from "~/utils/taskEvent"; +import { SpanLink } from "~/v3/eventRepository.server"; +import { RunTimelineEvent, RunTimelineLine } from "./InspectorTimeline"; +import { Spinner } from "~/components/primitives/Spinner"; +import { LiveTimer } from "./LiveTimer"; +import { formatDuration, nanosecondsToMilliseconds } from "@trigger.dev/core/v3"; + +export function SpanInspector({ + span, + runParam, + closePanel, +}: { + span?: TraceSpan; + runParam?: string; + closePanel?: () => void; +}) { + const organization = useOrganization(); + const project = useProject(); + const { value, replace } = useSearchParams(); + let tab = value("tab"); + + if (tab === "context") { + tab = "overview"; + } + + if (span === undefined) { + return null; + } + + return ( +
+
+
+ + + + +
+ {runParam && closePanel && ( +
+
+ + { + replace({ tab: "overview" }); + }} + shortcut={{ key: "o" }} + > + Overview + + { + replace({ tab: "detail" }); + }} + shortcut={{ key: "d" }} + > + Detail + + +
+
+
+ {tab === "detail" ? ( +
+ + + Status + + + + + + Task + + + {span.taskSlug} + + } + content={`Filter runs by ${span.taskSlug}`} + /> + + + {span.idempotencyKey && ( + + Idempotency key + {span.idempotencyKey} + + )} + + Version + + {span.workerVersion ? ( + span.workerVersion + ) : ( + + Never started + + + )} + + + {span.links && span.links.length > 0 && ( + + Links + +
+ {span.links.map((link, index) => ( + + ))} +
+
+
+ )} +
+
+ ) : ( +
+ {span.level === "TRACE" ? ( + <> +
+ +
+ + + ) : ( +
+ } + state="complete" + /> +
+ )} + + + Message + {span.message} + + {span.links && span.links.length > 0 && ( + + Links + +
+ {span.links.map((link, index) => ( + + ))} +
+
+
+ )} +
+ + {span.events !== undefined && } + {span.properties !== undefined && ( + + )} +
+ )} +
+
+
+ ); +} + +type TimelineProps = { + startTime: Date; + duration: number; + inProgress: boolean; + isError: boolean; +}; + +export function SpanTimeline({ startTime, duration, inProgress, isError }: TimelineProps) { + const state = isError ? "error" : inProgress ? "pending" : "complete"; + return ( + <> +
+ } + state="complete" + /> + {state === "pending" ? ( + + + + + + + } + state={"inprogress"} + /> + ) : ( + <> + + + } + state={isError ? "error" : "complete"} + /> + + )} +
+ + ); +} + +function SpanLinkElement({ link }: { link: SpanLink }) { + const organization = useOrganization(); + const project = useProject(); + + switch (link.type) { + case "run": { + return ( + + {link.title} + + ); + } + case "span": { + return ( + + {link.title} + + ); + } + } + + return null; +} diff --git a/apps/webapp/app/components/runs/v3/SpanTitle.tsx b/apps/webapp/app/components/runs/v3/SpanTitle.tsx index d065131a7..7e36652df 100644 --- a/apps/webapp/app/components/runs/v3/SpanTitle.tsx +++ b/apps/webapp/app/components/runs/v3/SpanTitle.tsx @@ -2,7 +2,6 @@ import { ChevronRightIcon } from "@heroicons/react/20/solid"; import { TaskEventStyle } from "@trigger.dev/core/v3"; import type { TaskEventLevel } from "@trigger.dev/database"; import { Fragment } from "react"; -import { RunEvent } from "~/presenters/v3/RunPresenter.server"; import { cn } from "~/utils/cn"; type SpanTitleProps = { @@ -107,9 +106,15 @@ function eventTextClassName(event: Pick -) { +type RunEvent = { + isError: boolean; + style: TaskEventStyle; + level: TaskEventLevel; + isPartial: boolean; + isCancelled: boolean; +}; + +export function eventBackgroundClassName(event: RunEvent) { if (event.isError) { return "bg-error"; } diff --git a/apps/webapp/app/env.server.ts b/apps/webapp/app/env.server.ts index 911e3ece8..9e6a5ab0e 100644 --- a/apps/webapp/app/env.server.ts +++ b/apps/webapp/app/env.server.ts @@ -31,6 +31,7 @@ const EnvironmentSchema = z.object({ REMIX_APP_PORT: z.string().optional(), LOGIN_ORIGIN: z.string().default("http://localhost:3030"), APP_ORIGIN: z.string().default("http://localhost:3030"), + ELECTRIC_ORIGIN: z.string(), APP_ENV: z.string().default(process.env.NODE_ENV), SERVICE_NAME: z.string().default("trigger.dev webapp"), SECRET_STORE: SecretStoreOptionsSchema.default("DATABASE"), diff --git a/apps/webapp/app/hooks/useSyncRunPage.ts b/apps/webapp/app/hooks/useSyncRunPage.ts new file mode 100644 index 000000000..a07c41bf5 --- /dev/null +++ b/apps/webapp/app/hooks/useSyncRunPage.ts @@ -0,0 +1,16 @@ +import { useSyncTrace } from "./useSyncTrace"; +import { useSyncTraceRuns } from "./useSyncTraceRuns"; + +type Params = { + origin: string; + traceId: string; +}; + +export function useSyncRunPage({ origin, traceId }: Params) { + const { isUpToDate: isRunUpToDate, runs } = useSyncTraceRuns({ origin, traceId }); + const { isUpToDate: isTraceUpToDate, events } = useSyncTrace({ origin, traceId }); + + const isUpToDate = isRunUpToDate && isTraceUpToDate; + + return { isUpToDate, isRunUpToDate, isTraceUpToDate, runs, events }; +} diff --git a/apps/webapp/app/hooks/useSyncTrace.ts b/apps/webapp/app/hooks/useSyncTrace.ts new file mode 100644 index 000000000..185c73fa4 --- /dev/null +++ b/apps/webapp/app/hooks/useSyncTrace.ts @@ -0,0 +1,19 @@ +import { useShape } from "@electric-sql/react"; +import { TaskEvent } from "@trigger.dev/database"; +import { createTraceTreeFromEvents } from "~/utils/taskEvent"; + +type Params = { + origin: string; + traceId: string; +}; + +export type Trace = ReturnType; +export type TraceEvent = Trace["events"][number]; + +export function useSyncTrace({ origin, traceId }: Params) { + const { isUpToDate, data, error, isError } = useShape({ + url: `${origin}/sync/traces/${traceId}`, + }); + + return { isUpToDate, events: data ? (data as any as TaskEvent[]) : undefined }; +} diff --git a/apps/webapp/app/hooks/useSyncTraceRuns.ts b/apps/webapp/app/hooks/useSyncTraceRuns.ts new file mode 100644 index 000000000..3e3c92771 --- /dev/null +++ b/apps/webapp/app/hooks/useSyncTraceRuns.ts @@ -0,0 +1,18 @@ +import { Prettify } from "@trigger.dev/core"; +import { TaskRun } from "@trigger.dev/database"; +import { SyncedShapeData, useSyncedShape } from "./useSyncedShape"; + +type Params = { + origin: string; + traceId: string; +}; + +export type RawRun = Prettify>; + +export function useSyncTraceRuns({ origin, traceId }: Params) { + const { isUpToDate, data } = useSyncedShape({ + url: `${origin}/sync/traces/runs/${traceId}`, + }); + + return { isUpToDate, runs: data }; +} diff --git a/apps/webapp/app/hooks/useSyncedShape.ts b/apps/webapp/app/hooks/useSyncedShape.ts new file mode 100644 index 000000000..77f37990d --- /dev/null +++ b/apps/webapp/app/hooks/useSyncedShape.ts @@ -0,0 +1,71 @@ +import { useShape } from "@electric-sql/react"; + +export type ShapeInput = Parameters[0]; +export type ShapeOutput = { + isError: boolean; + isUpToDate: boolean; + data: S[] | undefined; +}; + +export type SyncedShapeData = { + [K in keyof T]: T[K] extends Date + ? string + : T[K] extends Date | null + ? string | null + : T[K] extends BigInt + ? number + : T[K] extends BigInt | null + ? number | null + : T[K] extends object + ? SyncedShapeData + : T[K]; +}; + +export function useSyncedShape(props: ShapeInput): ShapeOutput> { + const output = useShape(props) as any; + + return { + isError: output.isError, + isUpToDate: output.isUpToDate, + data: output.data + ? (transformInput(output.data as InputObject) as SyncedShapeData[]) + : undefined, + }; +} + +type InputObject = { + [key: string]: Value; +}[]; + +type Value = + | string + | number + | boolean + | bigint + | null + | Value[] + | { + [key: string]: Value; + }; + +function transformInput(input: InputObject): SyncedShapeData { + return input.map((value) => transformValue(value)); +} + +function transformValue(value: Value): Value { + if (Array.isArray(value)) { + return value.map(transformValue); + } else if (typeof value === "object" && value !== null) { + const result: { [key: string]: Value } = {}; + + for (const key in value) { + result[key] = transformValue(value[key]); + } + + return result; + } else if (typeof value === "bigint") { + return Number(value); + } else { + return value; + } +} diff --git a/apps/webapp/app/presenters/v3/RunListPresenter.server.ts b/apps/webapp/app/presenters/v3/RunListPresenter.server.ts index a59bc5d02..9ce94f9d0 100644 --- a/apps/webapp/app/presenters/v3/RunListPresenter.server.ts +++ b/apps/webapp/app/presenters/v3/RunListPresenter.server.ts @@ -3,9 +3,9 @@ import parse from "parse-duration"; import { type Direction } from "~/components/runs/RunStatuses"; import { sqlDatabaseSchema } from "~/db.server"; import { displayableEnvironment } from "~/models/runtimeEnvironment.server"; -import { getAllTaskIdentifiers } from "~/models/task.server"; import { isCancellableRunStatus, isFinalRunStatus } from "~/v3/taskStatus"; import { BasePresenter } from "./basePresenter.server"; +import { getAllTaskIdentifiers } from "~/models/task.server"; export type RunListOptions = { userId?: string; diff --git a/apps/webapp/app/presenters/v3/RunPresenter.server.ts b/apps/webapp/app/presenters/v3/RunPresenter.server.ts index db67ed4c5..7e1e3ef1b 100644 --- a/apps/webapp/app/presenters/v3/RunPresenter.server.ts +++ b/apps/webapp/app/presenters/v3/RunPresenter.server.ts @@ -1,13 +1,9 @@ -import { millisecondsToNanoseconds } from "@trigger.dev/core/v3"; -import { createTreeFromFlatItems, flattenTree } from "~/components/primitives/TreeView/TreeView"; import { PrismaClient, prisma } from "~/db.server"; import { getUsername } from "~/utils/username"; -import { eventRepository } from "~/v3/eventRepository.server"; import { isFinalRunStatus } from "~/v3/taskStatus"; type Result = Awaited>; export type Run = Result["run"]; -export type RunEvent = NonNullable["events"][0]; export class RunPresenter { #prismaClient: PrismaClient; @@ -65,71 +61,6 @@ export class RunPresenter { }, }); - // get the events - const traceSummary = await eventRepository.getTraceSummary(run.traceId); - - if (!traceSummary) { - return { - run: { - id: run.id, - number: run.number, - friendlyId: run.friendlyId, - traceId: run.traceId, - spanId: run.spanId, - status: run.status, - isFinished: isFinalRunStatus(run.status), - completedAt: run.completedAt, - logsDeletedAt: run.logsDeletedAt, - environment: { - id: run.runtimeEnvironment.id, - organizationId: run.runtimeEnvironment.organizationId, - type: run.runtimeEnvironment.type, - slug: run.runtimeEnvironment.slug, - userId: run.runtimeEnvironment.orgMember?.user.id, - userName: getUsername(run.runtimeEnvironment.orgMember?.user), - }, - }, - trace: undefined, - }; - } - - //this tree starts at the passed in span (hides parent elements if there are any) - const tree = createTreeFromFlatItems(traceSummary.spans, run.spanId); - - //we need the start offset for each item, and the total duration of the entire tree - const treeRootStartTimeMs = tree ? tree?.data.startTime.getTime() : 0; - let totalDuration = tree?.data.duration ?? 0; - const events = tree - ? flattenTree(tree).map((n) => { - const offset = millisecondsToNanoseconds( - n.data.startTime.getTime() - treeRootStartTimeMs - ); - totalDuration = Math.max(totalDuration, offset + n.data.duration); - return { - ...n, - data: { - ...n.data, - //set partial nodes to null duration - duration: n.data.isPartial ? null : n.data.duration, - offset, - isRoot: n.id === traceSummary.rootSpan.id, - }, - }; - }) - : []; - - //total duration should be a minimum of 1ms - totalDuration = Math.max(totalDuration, millisecondsToNanoseconds(1)); - - let rootSpanStatus: "executing" | "completed" | "failed" = "executing"; - if (events[0]) { - if (events[0].data.isError) { - rootSpanStatus = "failed"; - } else if (!events[0].data.isPartial) { - rootSpanStatus = "completed"; - } - } - return { run: { id: run.id, @@ -141,6 +72,7 @@ export class RunPresenter { isFinished: isFinalRunStatus(run.status), completedAt: run.completedAt, logsDeletedAt: run.logsDeletedAt, + environment: { id: run.runtimeEnvironment.id, organizationId: run.runtimeEnvironment.organizationId, @@ -150,14 +82,6 @@ export class RunPresenter { userName: getUsername(run.runtimeEnvironment.orgMember?.user), }, }, - trace: { - rootSpanStatus, - events: events, - parentRunFriendlyId: - tree?.id === traceSummary.rootSpan.id ? undefined : traceSummary.rootSpan.runId, - duration: totalDuration, - rootStartedAt: tree?.data.startTime, - }, }; } } diff --git a/apps/webapp/app/presenters/v3/SpanPresenter.server.ts b/apps/webapp/app/presenters/v3/SpanPresenter.server.ts deleted file mode 100644 index 26400dce4..000000000 --- a/apps/webapp/app/presenters/v3/SpanPresenter.server.ts +++ /dev/null @@ -1,304 +0,0 @@ -import { MachinePresetName, prettyPrintPacket, TaskRunError } from "@trigger.dev/core/v3"; -import { RUNNING_STATUSES } from "~/components/runs/v3/TaskRunStatus"; -import { eventRepository } from "~/v3/eventRepository.server"; -import { machinePresetFromName } from "~/v3/machinePresets.server"; -import { FINAL_ATTEMPT_STATUSES, isFinalRunStatus } from "~/v3/taskStatus"; -import { BasePresenter } from "./basePresenter.server"; - -type Result = Awaited>; -export type Span = NonNullable["span"]>; -export type SpanRun = NonNullable["run"]>; - -export class SpanPresenter extends BasePresenter { - public async call({ - userId, - projectSlug, - organizationSlug, - spanId, - runFriendlyId, - }: { - userId: string; - projectSlug: string; - organizationSlug: string; - spanId: string; - runFriendlyId: string; - }) { - const project = await this._replica.project.findUnique({ - where: { - slug: projectSlug, - }, - }); - - if (!project) { - throw new Error("Project not found"); - } - - const run = await this.getRun(spanId); - if (run) { - return { - type: "run" as const, - run, - }; - } - - //get the run - const span = await this.getSpan(runFriendlyId, spanId); - - if (!span) { - throw new Error("Span not found"); - } - - return { - type: "span" as const, - span, - }; - } - - async getRun(spanId: string) { - const run = await this._replica.taskRun.findFirst({ - select: { - id: true, - traceId: true, - //metadata - number: true, - taskIdentifier: true, - friendlyId: true, - isTest: true, - tags: { - select: { - name: true, - }, - }, - machinePreset: true, - lockedToVersion: { - select: { - version: true, - sdkVersion: true, - }, - }, - //status + duration - status: true, - startedAt: true, - createdAt: true, - updatedAt: true, - queuedAt: true, - completedAt: true, - logsDeletedAt: true, - //idempotency - idempotencyKey: true, - //delayed - delayUntil: true, - //ttl - ttl: true, - expiredAt: true, - //queue - queue: true, - concurrencyKey: true, - //schedule - schedule: { - select: { - friendlyId: true, - generatorExpression: true, - timezone: true, - generatorDescription: true, - }, - }, - //usage - baseCostInCents: true, - costInCents: true, - usageDurationMs: true, - //env - runtimeEnvironment: { - select: { id: true, slug: true, type: true }, - }, - payload: true, - payloadType: true, - maxAttempts: true, - project: { - include: { - organization: true, - }, - }, - lockedBy: { - select: { - filePath: true, - exportName: true, - }, - }, - }, - where: { - spanId, - }, - }); - - if (!run) { - return; - } - - const isFinished = isFinalRunStatus(run.status); - - const finishedAttempt = isFinished - ? await this._replica.taskRunAttempt.findFirst({ - select: { - output: true, - outputType: true, - error: true, - }, - where: { - status: { in: FINAL_ATTEMPT_STATUSES }, - taskRunId: run.id, - }, - orderBy: { - createdAt: "desc", - }, - }) - : null; - - const output = - finishedAttempt === null - ? undefined - : finishedAttempt.outputType === "application/store" - ? `/resources/packets/${run.runtimeEnvironment.id}/${finishedAttempt.output}` - : typeof finishedAttempt.output !== "undefined" && finishedAttempt.output !== null - ? await prettyPrintPacket(finishedAttempt.output, finishedAttempt.outputType ?? undefined) - : undefined; - - const payload = - run.payloadType === "application/store" - ? `/resources/packets/${run.runtimeEnvironment.id}/${run.payload}` - : typeof run.payload !== "undefined" && run.payload !== null - ? await prettyPrintPacket(run.payload, run.payloadType ?? undefined) - : undefined; - - let error: TaskRunError | undefined = undefined; - if (finishedAttempt?.error) { - const result = TaskRunError.safeParse(finishedAttempt.error); - if (result.success) { - error = result.data; - } else { - error = { - type: "CUSTOM_ERROR", - raw: JSON.stringify(finishedAttempt.error), - }; - } - } - - const span = await eventRepository.getSpan(spanId, run.traceId); - - const context = { - task: { - id: run.taskIdentifier, - filePath: run.lockedBy?.filePath, - exportName: run.lockedBy?.exportName, - }, - run: { - id: run.friendlyId, - createdAt: run.createdAt, - tags: run.tags.map((tag) => tag.name), - isTest: run.isTest, - idempotencyKey: run.idempotencyKey ?? undefined, - startedAt: run.startedAt ?? run.createdAt, - durationMs: run.usageDurationMs, - costInCents: run.costInCents, - baseCostInCents: run.baseCostInCents, - maxAttempts: run.maxAttempts ?? undefined, - version: run.lockedToVersion?.version, - }, - queue: { - name: run.queue, - }, - environment: { - id: run.runtimeEnvironment.id, - slug: run.runtimeEnvironment.slug, - type: run.runtimeEnvironment.type, - }, - organization: { - id: run.project.organization.id, - slug: run.project.organization.slug, - name: run.project.organization.title, - }, - project: { - id: run.project.id, - ref: run.project.externalRef, - slug: run.project.slug, - name: run.project.name, - }, - machine: run.machinePreset - ? machinePresetFromName(run.machinePreset as MachinePresetName) - : undefined, - }; - - return { - friendlyId: run.friendlyId, - status: run.status, - createdAt: run.createdAt, - startedAt: run.startedAt, - updatedAt: run.updatedAt, - delayUntil: run.delayUntil, - expiredAt: run.expiredAt, - completedAt: run.completedAt, - logsDeletedAt: run.logsDeletedAt, - ttl: run.ttl, - taskIdentifier: run.taskIdentifier, - version: run.lockedToVersion?.version, - sdkVersion: run.lockedToVersion?.sdkVersion, - isTest: run.isTest, - environmentId: run.runtimeEnvironment.id, - schedule: run.schedule - ? { - friendlyId: run.schedule.friendlyId, - generatorExpression: run.schedule.generatorExpression, - description: run.schedule.generatorDescription, - timezone: run.schedule.timezone, - } - : undefined, - queue: { - name: run.queue, - isCustomQueue: !run.queue.startsWith("task/"), - concurrencyKey: run.concurrencyKey, - }, - tags: run.tags.map((tag) => tag.name), - baseCostInCents: run.baseCostInCents, - costInCents: run.costInCents, - totalCostInCents: run.costInCents + run.baseCostInCents, - usageDurationMs: run.usageDurationMs, - isFinished, - isRunning: RUNNING_STATUSES.includes(run.status), - payload, - payloadType: run.payloadType, - output, - outputType: finishedAttempt?.outputType ?? "application/json", - error, - links: span?.links, - context: JSON.stringify(context, null, 2), - }; - } - - async getSpan(runFriendlyId: string, spanId: string) { - const run = await this._prisma.taskRun.findFirst({ - select: { - traceId: true, - }, - where: { - friendlyId: runFriendlyId, - }, - }); - - if (!run) { - return; - } - - const span = await eventRepository.getSpan(spanId, run.traceId); - - if (!span) { - return; - } - - return { - ...span, - events: span.events, - properties: span.properties ? JSON.stringify(span.properties, null, 2) : undefined, - showActionBar: span.show?.actions === true, - }; - } -} diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.v3.$projectParam.deployments/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.v3.$projectParam.deployments/route.tsx index 0ec72c574..a73f2c43b 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.v3.$projectParam.deployments/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.v3.$projectParam.deployments/route.tsx @@ -2,16 +2,12 @@ import { ArrowPathIcon, ArrowUturnLeftIcon, BookOpenIcon, - CommandLineIcon, - ServerIcon, ServerStackIcon, } from "@heroicons/react/20/solid"; import { Outlet, useLocation, useParams } from "@remix-run/react"; import { LoaderFunctionArgs } from "@remix-run/server-runtime"; -import { Fragment } from "react/jsx-runtime"; import { typedjson, useTypedLoaderData } from "remix-typedjson"; import { z } from "zod"; -import { BlankstateInstructions } from "~/components/BlankstateInstructions"; import { UserAvatar } from "~/components/UserProfilePhoto"; import { EnvironmentLabel } from "~/components/environments/EnvironmentLabel"; import { MainCenteredContainer, PageBody, PageContainer } from "~/components/layout/AppLayout"; @@ -104,104 +100,108 @@ export default function Page() { - - + + {hasDeployments ? ( -
- - - - Deploy - Env - Version - Status - Tasks - Deployed at - Deployed by - Go to page - - - - {deployments.length > 0 ? ( - deployments.map((deployment) => { - const usernameForEnv = - user.id !== deployment.environment.userId - ? deployment.environment.userName - : undefined; - const path = v3DeploymentPath( - organization, - project, - deployment, - currentPage - ); - return ( - - -
- {deployment.shortCode} - {deployment.label && ( - {deployment.label} - )} -
-
- - - - {deployment.version} - - - - - {deployment.tasksCount !== null ? deployment.tasksCount : "–"} - - - {deployment.deployedAt ? ( - - ) : ( - "–" - )} - - - {deployment.deployedBy ? ( -
- +
+
+
+ + + Deploy + Env + Version + Status + Tasks + Deployed at + Deployed by + Go to page + + + + {deployments.length > 0 ? ( + deployments.map((deployment) => { + const usernameForEnv = + user.id !== deployment.environment.userId + ? deployment.environment.userName + : undefined; + const path = v3DeploymentPath( + organization, + project, + deployment, + currentPage + ); + return ( + + +
- {deployment.deployedBy.name ?? - deployment.deployedBy.displayName} + {deployment.shortCode} + {deployment.label && ( + {deployment.label} + )}
- ) : ( - "–" - )} -
- -
- ); - }) - ) : ( - - - No deploys match your filters - - - )} -
-
-
- + + + + + {deployment.version} + + + + + {deployment.tasksCount !== null ? deployment.tasksCount : "–"} + + + {deployment.deployedAt ? ( + + ) : ( + "–" + )} + + + {deployment.deployedBy ? ( +
+ + + {deployment.deployedBy.name ?? + deployment.deployedBy.displayName} + +
+ ) : ( + "–" + )} +
+ + + ); + }) + ) : ( + + + No deploys match your filters + + + )} + + +
+ +
) : ( @@ -211,8 +211,8 @@ export default function Page() { {deploymentParam && ( <> - - + + diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.v3.$projectParam.runs.$runParam/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.v3.$projectParam.runs.$runParam/route.tsx index 5c1641764..5859d6231 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.v3.$projectParam.runs.$runParam/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.v3.$projectParam.runs.$runParam/route.tsx @@ -1,6 +1,5 @@ import { ArrowUturnLeftIcon, - BoltSlashIcon, ChevronDownIcon, ChevronRightIcon, InformationCircleIcon, @@ -10,8 +9,8 @@ import { StopCircleIcon, } from "@heroicons/react/20/solid"; import type { Location } from "@remix-run/react"; -import { useLoaderData, useParams, useRevalidator } from "@remix-run/react"; -import { LoaderFunctionArgs, SerializeFrom, json } from "@remix-run/server-runtime"; +import { useParams } from "@remix-run/react"; +import { LoaderFunctionArgs } from "@remix-run/server-runtime"; import { Virtualizer } from "@tanstack/react-virtual"; import { formatDurationMilliseconds, @@ -20,8 +19,10 @@ import { } from "@trigger.dev/core/v3"; import { RuntimeEnvironmentType } from "@trigger.dev/database"; import { motion } from "framer-motion"; -import { useCallback, useEffect, useRef, useState } from "react"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useHotkeys } from "react-hotkeys-hook"; +import { UseDataFunctionReturn, typedjson, useTypedLoaderData } from "remix-typedjson"; +import { ClientOnly } from "remix-utils/client-only"; import { ShowParentIcon, ShowParentIconSelected } from "~/assets/icons/ShowParentIcon"; import tileBgPath from "~/assets/images/error-banner-tile@2x.png"; import { AdminDebugTooltip } from "~/components/admin/debugTooltip"; @@ -46,27 +47,31 @@ import { } from "~/components/primitives/Resizable"; import { ShortcutKey, variants } from "~/components/primitives/ShortcutKey"; import { Slider } from "~/components/primitives/Slider"; +import { Spinner } from "~/components/primitives/Spinner"; import { Switch } from "~/components/primitives/Switch"; import * as Timeline from "~/components/primitives/Timeline"; -import { SimpleTooltip } from "~/components/primitives/Tooltip"; import { TreeView, UseTreeStateOutput, useTree } from "~/components/primitives/TreeView/TreeView"; import { NodesState } from "~/components/primitives/TreeView/reducer"; import { CancelRunDialog } from "~/components/runs/v3/CancelRunDialog"; import { ReplayRunDialog } from "~/components/runs/v3/ReplayRunDialog"; import { RunIcon } from "~/components/runs/v3/RunIcon"; +import { RunInspector } from "~/components/runs/v3/RunInspector"; +import { SpanInspector } from "~/components/runs/v3/SpanInspector"; import { SpanTitle, eventBackgroundClassName } from "~/components/runs/v3/SpanTitle"; import { TaskRunStatusIcon, runStatusClassNameColor } from "~/components/runs/v3/TaskRunStatus"; -import { env } from "~/env.server"; +import { useAppOrigin } from "~/hooks/useAppOrigin"; import { useDebounce } from "~/hooks/useDebounce"; -import { useEventSource } from "~/hooks/useEventSource"; import { useInitialDimensions } from "~/hooks/useInitialDimensions"; import { useOrganization } from "~/hooks/useOrganizations"; import { useProject } from "~/hooks/useProject"; import { useReplaceLocation } from "~/hooks/useReplaceLocation"; import { Shortcut, useShortcutKeys } from "~/hooks/useShortcutKeys"; +import { useSyncRunPage } from "~/hooks/useSyncRunPage"; +import { Trace, TraceEvent } from "~/hooks/useSyncTrace"; +import { RawRun } from "~/hooks/useSyncTraceRuns"; import { useUser } from "~/hooks/useUser"; -import { RunPresenter } from "~/presenters/v3/RunPresenter.server"; -import { getResizableRunSettings, setResizableRunSettings } from "~/services/resizablePanel"; +import { Run, RunPresenter } from "~/presenters/v3/RunPresenter.server"; +import { getResizableSnapshot } from "~/services/resizablePanel.server"; import { requireUserId } from "~/services/session.server"; import { cn } from "~/utils/cn"; import { lerp } from "~/utils/lerp"; @@ -75,13 +80,45 @@ import { v3RunParamsSchema, v3RunPath, v3RunSpanPath, - v3RunStreamingPath, v3RunsPath, } from "~/utils/pathBuilder"; -import { SpanView } from "../resources.orgs.$organizationSlug.projects.v3.$projectParam.runs.$runParam.spans.$spanParam/route"; +import { + TraceSpan, + createSpanFromEvents, + createTraceTreeFromEvents, + prepareTrace, +} from "~/utils/taskEvent"; import { useCurrentPlan } from "../_app.orgs.$organizationSlug/route"; -type TraceEvent = NonNullable["trace"]>["events"][0]; +const resizableSettings = { + parent: { + autosaveId: "panel-run-parent", + handleId: "parent-handle", + main: { + id: "run", + min: "100px" as const, + }, + inspector: { + id: "inspector", + default: "430px" as const, + min: "50px" as const, + }, + }, + tree: { + autosaveId: "panel-run-tree", + handleId: "tree-handle", + tree: { + id: "tree", + default: "50%" as const, + min: "50px" as const, + }, + timeline: { + id: "timeline", + default: "50%" as const, + min: "50px" as const, + }, + }, +}; export const loader = async ({ request, params }: LoaderFunctionArgs) => { const userId = await requireUserId(request); @@ -96,17 +133,19 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => { }); //resizable settings - const resizeSettings = await getResizableRunSettings(request); + const parent = await getResizableSnapshot(request, resizableSettings.parent.autosaveId); + const tree = await getResizableSnapshot(request, resizableSettings.tree.autosaveId); - return json({ + return typedjson({ run: result.run, - trace: result.trace, - maximumLiveReloadingSetting: env.MAXIMUM_LIVE_RELOADING_EVENTS, - resizeSettings, + resizable: { + parent, + tree, + }, }); }; -type LoaderData = SerializeFrom; +type LoaderData = UseDataFunctionReturn; function getSpanId(location: Location): string | undefined { const search = new URLSearchParams(location.search); @@ -114,8 +153,8 @@ function getSpanId(location: Location): string | undefined { } export default function Page() { - const { run, trace, resizeSettings, maximumLiveReloadingSetting } = - useLoaderData(); + const { run, resizable } = useTypedLoaderData(); + const user = useUser(); const organization = useOrganization(); const project = useProject(); @@ -203,105 +242,172 @@ export default function Page() { - {trace ? ( - - ) : ( - - )} +
+ }> + {() => } + +
); } -function TraceView({ run, trace, maximumLiveReloadingSetting, resizeSettings }: LoaderData) { - const organization = useOrganization(); - const project = useProject(); +type InspectorState = + | { + type: "span"; + span?: TraceSpan; + } + | { + type: "run"; + run?: RawRun; + span?: TraceSpan; + } + | undefined; + +function Panels({ resizable, run: originalRun }: LoaderData) { const { location, replaceSearchParam } = useReplaceLocation(); const selectedSpanId = getSpanId(location); - if (!trace) { - return <>; - } - - const { events, parentRunFriendlyId, duration, rootSpanStatus, rootStartedAt } = trace; - const shouldLiveReload = events.length <= maximumLiveReloadingSetting; - - const changeToSpan = useDebounce((selectedSpan: string) => { - replaceSearchParam("span", selectedSpan); - }, 250); - - const revalidator = useRevalidator(); - const streamedEvents = useEventSource(v3RunStreamingPath(organization, project, run), { - event: "message", - disabled: !shouldLiveReload, + const appOrigin = useAppOrigin(); + const { isUpToDate, events, runs } = useSyncRunPage({ + origin: appOrigin, + traceId: originalRun.traceId, }); - useEffect(() => { - if (streamedEvents !== null) { - revalidator.revalidate(); + + const initialLoad = !isUpToDate || !runs; + + const trace = useMemo(() => { + if (!events) return undefined; + const preparedEvents = prepareTrace(events); + if (!preparedEvents) return undefined; + return createTraceTreeFromEvents(preparedEvents, originalRun.spanId); + }, [events, originalRun.spanId]); + + const inspectorState = useMemo(() => { + if (originalRun.logsDeletedAt) { + return { + type: "run", + run: runs?.find((r) => r.friendlyId === originalRun.friendlyId), + }; } - // WARNING Don't put the revalidator in the useEffect deps array or bad things will happen - }, [streamedEvents]); // eslint-disable-line react-hooks/exhaustive-deps + + if (selectedSpanId) { + if (runs && runs.length > 0) { + const spanRun = runs.find((r) => r.spanId === selectedSpanId); + if (spanRun && events) { + const span = createSpanFromEvents(events, selectedSpanId); + return { + type: "run", + run: spanRun, + span, + }; + } + } + + if (!events) { + return { + type: "span", + span: undefined, + }; + } + + const span = createSpanFromEvents(events, selectedSpanId); + return { + type: "span", + span, + }; + } + }, [selectedSpanId, runs, events]); return ( -
- { - if (layout.length !== 2) return; - if (!selectedSpanId) return; - setResizableRunSettings(document, layout); - }} - > - - { - //instantly close the panel if no span is selected - if (!selectedSpan) { - replaceSearchParam("span"); - return; - } - - changeToSpan(selectedSpan); - }} - totalDuration={duration} - rootSpanStatus={rootSpanStatus} - rootStartedAt={rootStartedAt ? new Date(rootStartedAt) : undefined} - environmentType={run.environment.type} - shouldLiveReload={shouldLiveReload} - maximumLiveReloadingSetting={maximumLiveReloadingSetting} + + + {initialLoad ? ( + + ) : ( + - - - {selectedSpanId && ( - - replaceSearchParam("span")} - /> - )} - -
+
+ + {inspectorState ? ( + + {inspectorState.type === "span" ? ( + replaceSearchParam("span") : undefined} + /> + ) : inspectorState.type === "run" ? ( + replaceSearchParam("span") : undefined} + /> + ) : null} + + ) : null} +
); } -function NoLogsView({ run, resizeSettings }: LoaderData) { +type TraceData = { + run: Run; + environmentType: RuntimeEnvironmentType; + trace?: Trace; + selectedSpanId: string | undefined; + replaceSearchParam: (key: string, value?: string) => void; +}; + +function TraceView({ run, environmentType, trace, selectedSpanId, replaceSearchParam }: TraceData) { + const changeToSpan = useDebounce((selectedSpan: string) => { + replaceSearchParam("span", selectedSpan); + }, 100); + + if (!trace) { + return ; + } + + const { events, parentRunFriendlyId, duration, rootSpanStatus, rootStartedAt } = trace; + + return ( + { + //instantly close the panel if no span is selected + if (!selectedSpan) { + replaceSearchParam("span"); + return; + } + + changeToSpan(selectedSpan); + }} + totalDuration={duration} + rootSpanStatus={rootSpanStatus} + rootStartedAt={rootStartedAt ? new Date(rootStartedAt) : undefined} + environmentType={environmentType} + /> + ); +} + +function NoLogsView({ run }: { run: Run }) { const plan = useCurrentPlan(); const organization = useOrganization(); @@ -318,68 +424,46 @@ function NoLogsView({ run, resizeSettings }: LoaderData) { daysSinceCompleted !== undefined && daysSinceCompleted <= logRetention; return ( -
- { - if (layout.length !== 2) return; - setResizableRunSettings(document, layout); - }} - > - -
- {daysSinceCompleted === undefined ? ( - - - We tidy up older logs to keep things running smoothly. - - - ) : isWithinLogRetention ? ( - - - Your log retention is {logRetention} days but these logs had already been deleted. - From now on only logs from runs that completed {logRetention} days ago will be - deleted. - - - ) : daysSinceCompleted <= 30 ? ( - - - The logs for this run have been deleted because the run completed{" "} - {daysSinceCompleted} days ago. - - Upgrade your plan to keep logs for longer. - - ) : ( - - - We tidy up older logs to keep things running smoothly. - - - )} -
-
- - - - -
+
+ {daysSinceCompleted === undefined ? ( + + + We tidy up older logs to keep things running smoothly. + + + ) : isWithinLogRetention ? ( + + + Your log retention is {logRetention} days but these logs had already been deleted. From + now on only logs from runs that completed {logRetention} days ago will be deleted. + + + ) : daysSinceCompleted <= 30 ? ( + + + The logs for this run have been deleted because the run completed {daysSinceCompleted}{" "} + days ago. + + Upgrade your plan to keep logs for longer. + + ) : ( + + + We tidy up older logs to keep things running smoothly. + + + )}
); } @@ -393,8 +477,6 @@ type TasksTreeViewProps = { rootSpanStatus: "executing" | "completed" | "failed"; rootStartedAt: Date | undefined; environmentType: RuntimeEnvironmentType; - shouldLiveReload: boolean; - maximumLiveReloadingSetting: number; }; function TasksTreeView({ @@ -406,8 +488,6 @@ function TasksTreeView({ rootSpanStatus, rootStartedAt, environmentType, - shouldLiveReload, - maximumLiveReloadingSetting, }: TasksTreeViewProps) { const [filterText, setFilterText] = useState(""); const [errorsOnly, setErrorsOnly] = useState(false); @@ -464,15 +544,14 @@ function TasksTreeView({ />
- { - if (layout.length !== 2) return; - setResizableRunSettings(document, layout); - }} - > + {/* Tree list */} - +
{parentRunFriendlyId ? ( @@ -482,11 +561,7 @@ function TasksTreeView({ This is the root task )} - +
- + {/* Timeline */} - +
-
+
- {isLiveReloading ? ( -
- - - Live reloading - -
- ) : ( - - - - Live reloading disabled - -
- } - > - )} - +
+ + + Live reloading + +
); } @@ -1023,7 +1078,7 @@ function SpanWithDuration({ )}
@@ -1083,7 +1138,7 @@ function ConnectedDevWarning() { useEffect(() => { const timer = setTimeout(() => { setIsVisible(true); - }, 3000); + }, 6000); return () => clearTimeout(timer); }, []); @@ -1091,14 +1146,14 @@ function ConnectedDevWarning() { return (
- Runs usually start within 1 second in{" "} + Runs usually start within 2 seconds in{" "} . Check you're running the CLI: npx trigger.dev@beta dev @@ -1215,3 +1270,17 @@ function SearchField({ onChange }: { onChange: (value: string) => void }) { /> ); } + +export function Loading() { + return ( +
+
+ + + Loading logs + +
+
+
+ ); +} diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.v3.$projectParam.schedules/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.v3.$projectParam.schedules/route.tsx index 53b4f31ec..70941ef5b 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.v3.$projectParam.schedules/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.v3.$projectParam.schedules/route.tsx @@ -177,8 +177,8 @@ export default function Page() { - - + +
{possibleTasks.length === 0 ? ( @@ -250,8 +250,8 @@ export default function Page() { {(isShowingNewPane || isShowingSchedule) && ( <> - - + + diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.v3.$projectParam.test.tasks.$taskParam/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.v3.$projectParam.test.tasks.$taskParam/route.tsx index 76cf92ecc..f8fbde5bd 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.v3.$projectParam.test.tasks.$taskParam/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.v3.$projectParam.test.tasks.$taskParam/route.tsx @@ -180,8 +180,8 @@ function StandardTaskForm({ task, runs }: { task: TestTask["task"]; runs: Standa onSubmit={(e) => submitForm(e)} > - - + +
- - + + - - + +
@@ -402,8 +402,8 @@ function ScheduledTaskForm({
- - + +
- - + +
@@ -117,8 +117,8 @@ export default function Page() {
) : hasSelectedEnvironment ? ( -
-
+
+
Select a task
{!rest.tasks?.length ? ( @@ -135,8 +135,8 @@ export default function Page() { )}
- - + + @@ -157,7 +157,7 @@ function TaskSelector({ return (
-
+
{ - const userId = await requireUserId(request); - const { projectParam, organizationSlug, runParam, spanParam } = v3SpanParamsSchema.parse(params); - - const presenter = new SpanPresenter(); - - try { - const result = await presenter.call({ - userId, - organizationSlug, - projectSlug: projectParam, - spanId: spanParam, - runFriendlyId: runParam, - }); - - return typedjson(result); - } catch (error) { - logger.error("Error loading span", { - projectParam, - organizationSlug, - runParam, - spanParam, - error, - }); - return redirectWithErrorMessage( - v3RunPath({ slug: organizationSlug }, { slug: projectParam }, { friendlyId: runParam }), - request, - `Event not found.` - ); - } -}; - -export function SpanView({ - runParam, - spanId, - closePanel, -}: { - runParam: string; - spanId: string | undefined; - closePanel?: () => void; -}) { - const organization = useOrganization(); - const project = useProject(); - const fetcher = useTypedFetcher(); - - useEffect(() => { - if (spanId === undefined) return; - fetcher.load( - `/resources/orgs/${organization.slug}/projects/v3/${project.slug}/runs/${runParam}/spans/${spanId}` - ); - }, [organization.slug, project.slug, runParam, spanId]); - - if (spanId === undefined) { - return null; - } - - if (fetcher.state !== "idle" || fetcher.data === undefined) { - return ( -
-
-
-
-
-
- -
-
- ); - } - - const { type } = fetcher.data; - - switch (type) { - case "run": { - return ( - - ); - } - case "span": { - return ; - } - } -} - -function SpanBody({ - span, - runParam, - closePanel, -}: { - span: Span; - runParam?: string; - closePanel?: () => void; -}) { - const organization = useOrganization(); - const project = useProject(); - const { value, replace } = useSearchParams(); - let tab = value("tab"); - - if (tab === "context") { - tab = "overview"; - } - - return ( -
-
-
- - - - -
- {runParam && closePanel && ( -
-
- - { - replace({ tab: "overview" }); - }} - shortcut={{ key: "o" }} - > - Overview - - { - replace({ tab: "detail" }); - }} - shortcut={{ key: "d" }} - > - Detail - - -
-
-
- {tab === "detail" ? ( -
- - - Status - - - - - - Task - - - {span.taskSlug} - - } - content={`Filter runs by ${span.taskSlug}`} - /> - - - {span.idempotencyKey && ( - - Idempotency key - {span.idempotencyKey} - - )} - - Version - - {span.workerVersion ? ( - span.workerVersion - ) : ( - - Never started - - - )} - - - {span.links && span.links.length > 0 && ( - - Links - -
- {span.links.map((link, index) => ( - - ))} -
-
-
- )} -
-
- ) : ( -
- {span.level === "TRACE" ? ( - <> -
- -
- - - ) : ( -
- } - state="complete" - /> -
- )} - - - Message - {span.message} - - {span.links && span.links.length > 0 && ( - - Links - -
- {span.links.map((link, index) => ( - - ))} -
-
-
- )} -
- - {span.events !== undefined && } - {span.properties !== undefined && ( - - )} -
- )} -
-
-
- ); -} - -function RunBody({ - run, - runParam, - spanId, - closePanel, -}: { - run: SpanRun; - runParam: string; - spanId: string; - closePanel?: () => void; -}) { - const organization = useOrganization(); - const project = useProject(); - const { value, replace } = useSearchParams(); - const tab = value("tab"); - - const environment = project.environments.find((e) => e.id === run.environmentId); - - return ( -
-
-
- - - {run.taskIdentifier} - -
- {runParam && closePanel && ( -
-
- - { - replace({ tab: "overview" }); - }} - shortcut={{ key: "o" }} - > - Overview - - { - replace({ tab: "detail" }); - }} - shortcut={{ key: "d" }} - > - Detail - - { - replace({ tab: "context" }); - }} - shortcut={{ key: "c" }} - > - Context - - -
-
-
- {tab === "detail" ? ( -
- - - Status - - - - - - Task - - - {run.taskIdentifier} - - } - content={`Filter runs by ${run.taskIdentifier}`} - /> - - - - Version - - {run.version ? ( - run.version - ) : ( - - Never started - - - )} - - - - SDK version - - {run.sdkVersion ? ( - run.sdkVersion - ) : ( - - Never started - - - )} - - - - Test run - - {run.isTest ? : "–"} - - - {environment && ( - - Environment - - - - - )} - {run.schedule && ( - - Schedule - -
-
- {run.schedule.generatorExpression} - ({run.schedule.timezone}) -
- - {run.schedule.description} - - } - content={`Go to schedule ${run.schedule.friendlyId}`} - /> -
-
-
- )} - - Queue - -
Name: {run.queue.name}
-
- Concurrency key: {run.queue.concurrencyKey ? run.queue.concurrencyKey : "–"} -
-
-
- - Time to live (TTL) - {run.ttl ?? "–"} - - - Tags - - {run.tags.length === 0 ? ( - "–" - ) : ( -
- {run.tags.map((tag) => ( - - - - } - content={`Filter runs by ${tag}`} - /> - ))} -
- )} -
-
- {run.links && run.links.length > 0 && ( - - Links - -
- {run.links.map((link, index) => ( - - ))} -
-
-
- )} - - Run invocation cost - - {run.baseCostInCents > 0 - ? formatCurrencyAccurate(run.baseCostInCents / 100) - : "–"} - - - - Compute cost - - {run.costInCents > 0 ? formatCurrencyAccurate(run.costInCents / 100) : "–"} - - - - Total cost - - {run.costInCents > 0 - ? formatCurrencyAccurate((run.baseCostInCents + run.costInCents) / 100) - : "–"} - - - - Usage duration - - {run.usageDurationMs > 0 - ? formatDurationMilliseconds(run.usageDurationMs, { style: "short" }) - : "–"} - - -
-
- ) : tab === "context" ? ( -
- -
- ) : ( -
-
- -
- - {run.payload !== undefined && ( - - )} - {run.error !== undefined ? ( - - ) : run.output !== undefined ? ( - - ) : null} -
- )} -
-
-
-
- {run.friendlyId !== runParam && ( - - Focus on run - - )} -
-
- {run.logsDeletedAt === null ? ( - - Download logs - - ) : null} -
-
-
- ); -} - -function RunTimeline({ run }: { run: SpanRun }) { - return ( -
- } - state="complete" - /> - {run.delayUntil && !run.expiredAt ? ( - {formatDuration(run.createdAt, run.delayUntil)} delay - ) : ( - - - - Delayed until {run.ttl && <>(TTL {run.ttl})} - - - ) - } - state={run.startedAt ? "complete" : "delayed"} - /> - ) : run.startedAt ? ( - - ) : ( - - {" "} - {run.ttl && <>(TTL {run.ttl})} - - } - state={run.startedAt || run.expiredAt ? "complete" : "inprogress"} - /> - )} - {run.expiredAt ? ( - } - state="error" - /> - ) : run.startedAt ? ( - <> - } - state="complete" - /> - {run.isFinished ? ( - <> - - } - state="complete" - /> - - ) : ( - - - - - - - } - state={"inprogress"} - /> - )} - - ) : null} -
- ); -} - -type RunTimelineItemProps = { - title: ReactNode; - subtitle?: ReactNode; - state: "complete" | "error"; -}; - -function RunTimelineEvent({ title, subtitle, state }: RunTimelineItemProps) { - return ( -
-
-
-
-
- {title} - {subtitle ? {subtitle} : null} -
-
- ); -} - -type RunTimelineLineProps = { - title: ReactNode; - state: "complete" | "delayed" | "inprogress"; -}; - -function RunTimelineLine({ title, state }: RunTimelineLineProps) { - return ( -
-
-
-
-
- {title} -
-
- ); -} - -function RunError({ error }: { error: TaskRunError }) { - switch (error.type) { - case "STRING_ERROR": - case "CUSTOM_ERROR": { - return ( -
- -
- ); - } - case "BUILT_IN_ERROR": - case "INTERNAL_ERROR": { - const name = "name" in error ? error.name : error.code; - return ( -
- {name} - {error.message && {error.message}} - {error.stackTrace && ( - - )} -
- ); - } - } -} - -function PacketDisplay({ - data, - dataType, - title, -}: { - data: string; - dataType: string; - title: string; -}) { - switch (dataType) { - case "application/store": { - return ( -
- - {title} - - - Download - -
- ); - } - case "text/plain": { - return ( - - ); - } - default: { - return ( - - ); - } - } -} - -type TimelineProps = { - startTime: Date; - duration: number; - inProgress: boolean; - isError: boolean; -}; - -type TimelineState = "error" | "pending" | "complete"; - -function SpanTimeline({ startTime, duration, inProgress, isError }: TimelineProps) { - const state = isError ? "error" : inProgress ? "pending" : "complete"; - return ( - <> -
- } - state="complete" - /> - {state === "pending" ? ( - - - - - - - } - state={"inprogress"} - /> - ) : ( - <> - - - } - state={isError ? "error" : "complete"} - /> - - )} -
- - ); -} - -function VerticalBar({ state }: { state: TimelineState }) { - return
; -} - -function DottedLine() { - return ( -
-
-
-
-
-
- ); -} - -function classNameForState(state: TimelineState) { - switch (state) { - case "pending": { - return "bg-pending"; - } - case "complete": { - return "bg-success"; - } - case "error": { - return "bg-error"; - } - } -} - -function SpanLinkElement({ link }: { link: SpanLink }) { - const organization = useOrganization(); - const project = useProject(); - - switch (link.type) { - case "run": { - return ( - - {link.title} - - ); - } - case "span": { - return ( - - {link.title} - - ); - } - } - - return null; -} diff --git a/apps/webapp/app/routes/resources.runs.$runParam.logs.download.ts b/apps/webapp/app/routes/resources.runs.$runParam.logs.download.ts index 6c218d0e3..3b9dc5912 100644 --- a/apps/webapp/app/routes/resources.runs.$runParam.logs.download.ts +++ b/apps/webapp/app/routes/resources.runs.$runParam.logs.download.ts @@ -2,15 +2,11 @@ import { LoaderFunctionArgs } from "@remix-run/server-runtime"; import { prisma } from "~/db.server"; import { requireUserId } from "~/services/session.server"; import { v3RunParamsSchema } from "~/utils/pathBuilder"; -import { - PreparedEvent, - RunPreparedEvent, - eventRepository, - getDateFromNanoseconds, -} from "~/v3/eventRepository.server"; +import { PreparedEvent, RunPreparedEvent, eventRepository } from "~/v3/eventRepository.server"; import { createGzip } from "zlib"; import { Readable } from "stream"; import { formatDurationMilliseconds } from "@trigger.dev/core/v3/utils/durations"; +import { getDateFromNanoseconds } from "~/utils/taskEvent"; export async function loader({ params, request }: LoaderFunctionArgs) { const userId = await requireUserId(request); diff --git a/apps/webapp/app/routes/resources.runs.$runParam.ts b/apps/webapp/app/routes/resources.runs.$runParam.ts new file mode 100644 index 000000000..744dff796 --- /dev/null +++ b/apps/webapp/app/routes/resources.runs.$runParam.ts @@ -0,0 +1,240 @@ +import { LoaderFunctionArgs } from "@remix-run/server-runtime"; +import { MachinePresetName, prettyPrintPacket, TaskRunError } from "@trigger.dev/core/v3"; +import { typedjson, UseDataFunctionReturn } from "remix-typedjson"; +import { RUNNING_STATUSES } from "~/components/runs/v3/TaskRunStatus"; +import { $replica } from "~/db.server"; +import { requireUserId } from "~/services/session.server"; +import { v3RunParamsSchema } from "~/utils/pathBuilder"; +import { machinePresetFromName } from "~/v3/machinePresets.server"; +import { FINAL_ATTEMPT_STATUSES, isFinalRunStatus } from "~/v3/taskStatus"; + +export type RunInspectorData = UseDataFunctionReturn; + +export const loader = async ({ request, params }: LoaderFunctionArgs) => { + const userId = await requireUserId(request); + const parsedParams = v3RunParamsSchema.pick({ runParam: true }).parse(params); + + const run = await $replica.taskRun.findFirst({ + select: { + id: true, + traceId: true, + //metadata + number: true, + taskIdentifier: true, + friendlyId: true, + isTest: true, + tags: { + select: { + name: true, + }, + }, + machinePreset: true, + lockedToVersion: { + select: { + version: true, + sdkVersion: true, + }, + }, + //status + duration + status: true, + startedAt: true, + createdAt: true, + updatedAt: true, + queuedAt: true, + completedAt: true, + logsDeletedAt: true, + //idempotency + idempotencyKey: true, + //delayed + delayUntil: true, + //ttl + ttl: true, + expiredAt: true, + //queue + queue: true, + concurrencyKey: true, + //schedule + schedule: { + select: { + friendlyId: true, + generatorExpression: true, + timezone: true, + generatorDescription: true, + }, + }, + //usage + baseCostInCents: true, + costInCents: true, + usageDurationMs: true, + //env + runtimeEnvironment: { + select: { id: true, slug: true, type: true }, + }, + payload: true, + payloadType: true, + maxAttempts: true, + project: { + include: { + organization: true, + }, + }, + lockedBy: { + select: { + filePath: true, + exportName: true, + }, + }, + }, + where: { + friendlyId: parsedParams.runParam, + project: { + organization: { + members: { + some: { + userId, + }, + }, + }, + }, + }, + }); + + if (!run) { + throw new Response("Not found", { status: 404 }); + } + + const isFinished = isFinalRunStatus(run.status); + + const finishedAttempt = isFinished + ? await $replica.taskRunAttempt.findFirst({ + select: { + output: true, + outputType: true, + error: true, + }, + where: { + status: { in: FINAL_ATTEMPT_STATUSES }, + taskRunId: run.id, + }, + orderBy: { + createdAt: "desc", + }, + }) + : null; + + const output = + finishedAttempt === null + ? undefined + : finishedAttempt.outputType === "application/store" + ? `/resources/packets/${run.runtimeEnvironment.id}/${finishedAttempt.output}` + : typeof finishedAttempt.output !== "undefined" && finishedAttempt.output !== null + ? await prettyPrintPacket(finishedAttempt.output, finishedAttempt.outputType ?? undefined) + : undefined; + + const payload = + run.payloadType === "application/store" + ? `/resources/packets/${run.runtimeEnvironment.id}/${run.payload}` + : typeof run.payload !== "undefined" && run.payload !== null + ? await prettyPrintPacket(run.payload, run.payloadType ?? undefined) + : undefined; + + let error: TaskRunError | undefined = undefined; + if (finishedAttempt?.error) { + const result = TaskRunError.safeParse(finishedAttempt.error); + if (result.success) { + error = result.data; + } else { + error = { + type: "CUSTOM_ERROR", + raw: JSON.stringify(finishedAttempt.error), + }; + } + } + + const context = { + task: { + id: run.taskIdentifier, + filePath: run.lockedBy?.filePath, + exportName: run.lockedBy?.exportName, + }, + run: { + id: run.friendlyId, + createdAt: run.createdAt, + tags: run.tags.map((tag) => tag.name), + isTest: run.isTest, + idempotencyKey: run.idempotencyKey ?? undefined, + startedAt: run.startedAt ?? run.createdAt, + durationMs: run.usageDurationMs, + costInCents: run.costInCents, + baseCostInCents: run.baseCostInCents, + maxAttempts: run.maxAttempts ?? undefined, + version: run.lockedToVersion?.version, + }, + queue: { + name: run.queue, + }, + environment: { + id: run.runtimeEnvironment.id, + slug: run.runtimeEnvironment.slug, + type: run.runtimeEnvironment.type, + }, + organization: { + id: run.project.organization.id, + slug: run.project.organization.slug, + name: run.project.organization.title, + }, + project: { + id: run.project.id, + ref: run.project.externalRef, + slug: run.project.slug, + name: run.project.name, + }, + machine: run.machinePreset + ? machinePresetFromName(run.machinePreset as MachinePresetName) + : undefined, + }; + + return typedjson({ + friendlyId: run.friendlyId, + status: run.status, + createdAt: run.createdAt, + startedAt: run.startedAt, + updatedAt: run.updatedAt, + delayUntil: run.delayUntil, + expiredAt: run.expiredAt, + completedAt: run.completedAt, + logsDeletedAt: run.logsDeletedAt, + ttl: run.ttl, + taskIdentifier: run.taskIdentifier, + version: run.lockedToVersion?.version, + sdkVersion: run.lockedToVersion?.sdkVersion, + isTest: run.isTest, + environmentId: run.runtimeEnvironment.id, + schedule: run.schedule + ? { + friendlyId: run.schedule.friendlyId, + generatorExpression: run.schedule.generatorExpression, + description: run.schedule.generatorDescription, + timezone: run.schedule.timezone, + } + : undefined, + queue: { + name: run.queue, + isCustomQueue: !run.queue.startsWith("task/"), + concurrencyKey: run.concurrencyKey, + }, + tags: run.tags.map((tag) => tag.name), + baseCostInCents: run.baseCostInCents, + costInCents: run.costInCents, + totalCostInCents: run.costInCents + run.baseCostInCents, + usageDurationMs: run.usageDurationMs, + isFinished, + isRunning: RUNNING_STATUSES.includes(run.status), + payload, + payloadType: run.payloadType, + output, + outputType: finishedAttempt?.outputType ?? "application/json", + error, + context: JSON.stringify(context, null, 2), + }); +}; diff --git a/apps/webapp/app/routes/storybook.resizable/route.tsx b/apps/webapp/app/routes/storybook.resizable/route.tsx new file mode 100644 index 000000000..109b7b5af --- /dev/null +++ b/apps/webapp/app/routes/storybook.resizable/route.tsx @@ -0,0 +1,24 @@ +import { + ResizableHandle, + ResizablePanel, + ResizablePanelGroup, +} from "~/components/primitives/Resizable"; + +export default function Story() { + return ( +
+ + + + + +
+ ); +} diff --git a/apps/webapp/app/routes/storybook/route.tsx b/apps/webapp/app/routes/storybook/route.tsx index ad8e61b0b..b6ac587ff 100644 --- a/apps/webapp/app/routes/storybook/route.tsx +++ b/apps/webapp/app/routes/storybook/route.tsx @@ -72,6 +72,10 @@ const stories: Story[] = [ name: "Radio group", slug: "radio-group", }, + { + name: "Resizable", + slug: "resizable", + }, { name: "Segemented control", slug: "segmented-control", diff --git a/apps/webapp/app/routes/sync.traces.$traceId.ts b/apps/webapp/app/routes/sync.traces.$traceId.ts new file mode 100644 index 000000000..1a06dd488 --- /dev/null +++ b/apps/webapp/app/routes/sync.traces.$traceId.ts @@ -0,0 +1,69 @@ +import type { LoaderFunctionArgs } from "@remix-run/node"; +import { $replica } from "~/db.server"; +import { env } from "~/env.server"; +import { logger } from "~/services/logger.server"; +import { getUserId } from "~/services/session.server"; +import { longPollingFetch } from "~/utils/longPollingFetch"; + +export async function loader({ params, request }: LoaderFunctionArgs) { + try { + const userId = await getUserId(request); + + logger.log(`/sync/traces/${params.traceId}`, { userId }); + + if (!userId) { + return new Response("No user found in cookie", { status: 401 }); + } + + const trace = await $replica.taskEvent.findFirst({ + select: { + organizationId: true, + }, + where: { + traceId: params.traceId, + }, + }); + + if (!trace) { + return new Response("No trace found", { status: 404 }); + } + + const member = await $replica.orgMember.findFirst({ + where: { + organizationId: trace.organizationId, + userId, + }, + }); + + if (!member) { + return new Response("Not a member of this org", { status: 401 }); + } + + const url = new URL(request.url); + const originUrl = new URL(`${env.ELECTRIC_ORIGIN}/v1/shape/public."TaskEvent"`); + url.searchParams.forEach((value, key) => { + originUrl.searchParams.set(key, value); + }); + + originUrl.searchParams.set("where", `"traceId"='${params.traceId}'`); + + const finalUrl = originUrl.toString(); + + logger.log("Fetching trace data", { url: finalUrl }); + + return longPollingFetch(finalUrl); + } catch (error) { + if (error instanceof Response) { + // Error responses from longPollingFetch + return error; + } else if (error instanceof TypeError) { + // Unexpected errors + logger.error("Unexpected error in loader:", { error: error.message }); + return new Response("An unexpected error occurred", { status: 500 }); + } else { + // Unknown errors + logger.error("Unknown error occurred in loader, not Error", { error: JSON.stringify(error) }); + return new Response("An unknown error occurred", { status: 500 }); + } + } +} diff --git a/apps/webapp/app/routes/sync.traces.runs.$traceId.ts b/apps/webapp/app/routes/sync.traces.runs.$traceId.ts new file mode 100644 index 000000000..279e2ffa5 --- /dev/null +++ b/apps/webapp/app/routes/sync.traces.runs.$traceId.ts @@ -0,0 +1,79 @@ +import type { LoaderFunctionArgs } from "@remix-run/node"; +import { z } from "zod"; +import { $replica } from "~/db.server"; +import { env } from "~/env.server"; +import { logger } from "~/services/logger.server"; +import { getUserId } from "~/services/session.server"; +import { longPollingFetch } from "~/utils/longPollingFetch"; + +const Params = z.object({ + traceId: z.string(), +}); + +export async function loader({ params, request }: LoaderFunctionArgs) { + try { + const userId = await getUserId(request); + const { traceId } = Params.parse(params); + + logger.log(`/sync/runs/${traceId}`, { userId }); + + if (!userId) { + return new Response("No user found in cookie", { status: 401 }); + } + + const run = await $replica.taskRun.findFirst({ + select: { + project: { + select: { + organizationId: true, + }, + }, + }, + where: { + traceId, + }, + }); + + if (!run) { + return new Response("No run found", { status: 404 }); + } + + const member = await $replica.orgMember.findFirst({ + where: { + organizationId: run.project.organizationId, + userId, + }, + }); + + if (!member) { + return new Response("Not a member of this org", { status: 401 }); + } + + const url = new URL(request.url); + const originUrl = new URL(`${env.ELECTRIC_ORIGIN}/v1/shape/public."TaskRun"`); + url.searchParams.forEach((value, key) => { + originUrl.searchParams.set(key, value); + }); + + originUrl.searchParams.set("where", `"traceId"='${traceId}'`); + + const finalUrl = originUrl.toString(); + + logger.log("Fetching trace runs data", { url: finalUrl }); + + return longPollingFetch(finalUrl); + } catch (error) { + if (error instanceof Response) { + // Error responses from longPollingFetch + return error; + } else if (error instanceof TypeError) { + // Unexpected errors + logger.error("Unexpected error in loader:", { error: error.message }); + return new Response("An unexpected error occurred", { status: 500 }); + } else { + // Unknown errors + logger.error("Unknown error occurred in loader, not Error", { error: JSON.stringify(error) }); + return new Response("An unknown error occurred", { status: 500 }); + } + } +} diff --git a/apps/webapp/app/services/resizablePanel.server.ts b/apps/webapp/app/services/resizablePanel.server.ts new file mode 100644 index 000000000..f24e92770 --- /dev/null +++ b/apps/webapp/app/services/resizablePanel.server.ts @@ -0,0 +1,39 @@ +import { parse } from "cookie"; +import { type ResizableSnapshot } from "~/components/primitives/Resizable"; +import { logger } from "./logger.server"; + +export async function getResizableSnapshot( + request: Request, + id: string +): Promise { + try { + const cookieHeader = request.headers.get("Cookie"); + if (!cookieHeader) { + return undefined; + } + + const cookies = parse(cookieHeader); + const cookieValue = cookies[id]; + + if (cookieValue) { + try { + const parsedValue = JSON.parse(cookieValue) as any; + if (typeof parsedValue === "object" && "status" in parsedValue) { + return parsedValue as ResizableSnapshot; + } + } catch (parseError) { + logger.error("getResizableSnapshot() error parsing cookie value:", { + parseError, + cookieValue, + }); + } + } + + return undefined; + } catch (error) { + logger.error("getResizableSnapshot() error:", { + error, + }); + return undefined; + } +} diff --git a/apps/webapp/app/services/resizablePanel.ts b/apps/webapp/app/services/resizablePanel.ts deleted file mode 100644 index e3196a1c8..000000000 --- a/apps/webapp/app/services/resizablePanel.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { z } from "zod"; - -const ResizableConfig = z - .object({ layout: z.array(z.number()).optional() }) - .default({ layout: undefined }); -type ResizableConfig = z.infer; - -function getCookieValue(cookieHeader: string | null, cookieName: string): ResizableConfig { - const cookieValue = cookieHeader?.split(`${cookieName}=`)[1]?.split(";")[0]; - if (!cookieValue) { - return { layout: undefined }; - } - try { - const json = JSON.parse(cookieValue); - return ResizableConfig.parse(json); - } catch (e) { - return { layout: undefined }; - } -} - -//run page -const runResizableName = "resizable-panels:run"; - -export async function getResizableRunSettings(request: Request): Promise { - const cookieHeader = request.headers.get("Cookie"); - return getCookieValue(cookieHeader, runResizableName); -} - -export async function setResizableRunSettings(document: Document, layout: number[]) { - document.cookie = `${runResizableName}=${JSON.stringify({ layout })}`; -} diff --git a/apps/webapp/app/utils/longPollingFetch.ts b/apps/webapp/app/utils/longPollingFetch.ts new file mode 100644 index 000000000..7d183d3ac --- /dev/null +++ b/apps/webapp/app/utils/longPollingFetch.ts @@ -0,0 +1,46 @@ +// When proxying long-polling requests, content-encoding & content-length are added +// erroneously (saying the body is gzipped when it's not) so we'll just remove +// them to avoid content decoding errors in the browser. +// + +import { logger } from "~/services/logger.server"; + +// Similar-ish problem to https://github.com/wintercg/fetch/issues/23 +export async function longPollingFetch(url: string, options?: RequestInit) { + try { + let response = await fetch(url, options); + + // Check if the response is ok (status in the range 200-299) + if (!response.ok) { + const body = await response.text(); + throw new Error(`HTTP error! status: ${response.status}. ${body}`); + } + + if (response.headers.get(`content-encoding`)) { + const headers = new Headers(response.headers); + headers.delete(`content-encoding`); + headers.delete(`content-length`); + response = new Response(response.body, { + status: response.status, + statusText: response.statusText, + headers, + }); + } + + return response; + } catch (error) { + if (error instanceof TypeError) { + // Network error or other fetch-related errors + logger.error("Network error:", { error: error.message }); + throw new Response("Network error occurred", { status: 503 }); + } else if (error instanceof Error) { + // HTTP errors or other known errors + logger.error("Fetch error:", { error: error.message }); + throw new Response(error.message, { status: 500 }); + } else { + // Unknown errors + logger.error("Unknown error occurred during fetch"); + throw new Response("An unknown error occurred", { status: 500 }); + } + } +} diff --git a/apps/webapp/app/utils/taskEvent.ts b/apps/webapp/app/utils/taskEvent.ts new file mode 100644 index 000000000..b42530a2b --- /dev/null +++ b/apps/webapp/app/utils/taskEvent.ts @@ -0,0 +1,570 @@ +import { Attributes, Link } from "@opentelemetry/api"; +import { + correctErrorStackTrace, + ExceptionEventProperties, + isExceptionSpanEvent, + millisecondsToNanoseconds, + NULL_SENTINEL, + SemanticInternalAttributes, + SpanEvent, + SpanEvents, + SpanMessagingEvent, + TaskEventStyle, + unflattenAttributes, +} from "@trigger.dev/core/v3"; +import { Prisma, TaskEvent } from "@trigger.dev/database"; +import { createTreeFromFlatItems, flattenTree } from "~/components/primitives/TreeView/TreeView"; +import type { + PreparedEvent, + SpanLink, + SpanSummary, + TraceSummary, +} from "~/v3/eventRepository.server"; + +export type TraceSpan = NonNullable>; + +export function prepareTrace(events: TaskEvent[]): TraceSummary | undefined { + let preparedEvents: Array = []; + let rootSpanId: string | undefined; + const eventsBySpanId = new Map(); + + for (const event of events) { + preparedEvents.push(prepareEvent(event)); + + if (!rootSpanId && !event.parentId) { + rootSpanId = event.spanId; + } + } + + for (const event of preparedEvents) { + const existingEvent = eventsBySpanId.get(event.spanId); + + if (!existingEvent) { + eventsBySpanId.set(event.spanId, event); + continue; + } + + if (event.isCancelled || !event.isPartial) { + eventsBySpanId.set(event.spanId, event); + } + } + + preparedEvents = Array.from(eventsBySpanId.values()); + + const spansBySpanId = new Map(); + + const spans = preparedEvents.map((event) => { + const ancestorCancelled = isAncestorCancelled(eventsBySpanId, event.spanId); + const duration = calculateDurationIfAncestorIsCancelled( + eventsBySpanId, + event.spanId, + event.duration + ); + + const span = { + recordId: event.id, + id: event.spanId, + parentId: event.parentId ?? undefined, + runId: event.runId, + idempotencyKey: event.idempotencyKey, + data: { + message: event.message, + style: event.style, + duration, + isError: event.isError, + isPartial: ancestorCancelled ? false : event.isPartial, + isCancelled: event.isCancelled === true ? true : event.isPartial && ancestorCancelled, + startTime: getDateFromNanoseconds(event.startTime), + level: event.level, + events: event.events, + environmentType: event.environmentType, + }, + }; + + spansBySpanId.set(event.spanId, span); + + return span; + }); + + if (!rootSpanId) { + return; + } + + const rootSpan = spansBySpanId.get(rootSpanId); + + if (!rootSpan) { + return; + } + + return { + rootSpan, + spans, + }; +} + +export function createTraceTreeFromEvents(traceSummary: TraceSummary, spanId: string) { + //this tree starts at the passed in span (hides parent elements if there are any) + const tree = createTreeFromFlatItems(traceSummary.spans, spanId); + + //we need the start offset for each item, and the total duration of the entire tree + const treeRootStartTimeMs = tree ? tree?.data.startTime.getTime() : 0; + let totalDuration = tree?.data.duration ?? 0; + const events = tree + ? flattenTree(tree).map((n) => { + const offset = millisecondsToNanoseconds(n.data.startTime.getTime() - treeRootStartTimeMs); + totalDuration = Math.max(totalDuration, offset + n.data.duration); + return { + ...n, + data: { + ...n.data, + //set partial nodes to null duration + duration: n.data.isPartial ? null : n.data.duration, + offset, + isRoot: n.id === traceSummary.rootSpan.id, + }, + }; + }) + : []; + + //total duration should be a minimum of 1ms + totalDuration = Math.max(totalDuration, millisecondsToNanoseconds(1)); + + let rootSpanStatus: "executing" | "completed" | "failed" = "executing"; + if (events[0]) { + if (events[0].data.isError) { + rootSpanStatus = "failed"; + } else if (!events[0].data.isPartial) { + rootSpanStatus = "completed"; + } + } + + return { + rootSpanStatus, + events: events, + parentRunFriendlyId: + tree?.id === traceSummary.rootSpan.id ? undefined : traceSummary.rootSpan.runId, + duration: totalDuration, + rootStartedAt: tree?.data.startTime, + }; +} + +export function createSpanFromEvents(events: TaskEvent[], spanId: string) { + const spanEvent = getSpanEvent(events, spanId); + + if (!spanEvent) { + return; + } + + const preparedEvent = prepareEvent(spanEvent); + const span = createSpanFromEvent(events, preparedEvent); + + const output = rehydrateJson(spanEvent.output); + const payload = rehydrateJson(spanEvent.payload); + + const show = rehydrateShow(spanEvent.properties); + + const properties = sanitizedAttributes(spanEvent.properties); + + const messagingEvent = SpanMessagingEvent.optional().safeParse((properties as any)?.messaging); + + const links: SpanLink[] = []; + + if (messagingEvent.success && messagingEvent.data) { + if (messagingEvent.data.message && "id" in messagingEvent.data.message) { + if (messagingEvent.data.message.id.startsWith("run_")) { + links.push({ + type: "run", + icon: "runs", + title: `Run ${messagingEvent.data.message.id}`, + runId: messagingEvent.data.message.id, + }); + } + } + } + + const backLinks = spanEvent.links as any as Link[] | undefined; + + if (backLinks && backLinks.length > 0) { + backLinks.forEach((l) => { + const title = String(l.attributes?.[SemanticInternalAttributes.LINK_TITLE] ?? "Triggered by"); + + links.push({ + type: "span", + icon: "trigger", + title, + traceId: l.context.traceId, + spanId: l.context.spanId, + }); + }); + } + + const spanEvents = transformEvents( + preparedEvent.events, + spanEvent.metadata as Attributes, + spanEvent.environmentType === "DEVELOPMENT" + ); + + return { + ...spanEvent, + ...span.data, + payload, + output, + events: spanEvents, + show, + links, + properties: properties ? JSON.stringify(properties, null, 2) : undefined, + showActionBar: show?.actions === true, + }; +} + +function createSpanFromEvent(events: TaskEvent[], event: PreparedEvent) { + let ancestorCancelled = false; + let duration = event.duration; + + if (!event.isCancelled && event.isPartial) { + walkSpanAncestors(events, event, (ancestorEvent, level) => { + if (level >= 8) { + return { stop: true }; + } + + if (ancestorEvent.isCancelled) { + ancestorCancelled = true; + + // We need to get the cancellation time from the cancellation span event + const cancellationEvent = ancestorEvent.events.find( + (event) => event.name === "cancellation" + ); + + if (cancellationEvent) { + duration = calculateDurationFromStart(event.startTime, cancellationEvent.time); + } + + return { stop: true }; + } + + return { stop: false }; + }); + } + + const span = { + recordId: event.id, + id: event.spanId, + parentId: event.parentId ?? undefined, + runId: event.runId, + idempotencyKey: event.idempotencyKey, + data: { + message: event.message, + style: event.style, + duration, + isError: event.isError, + isPartial: ancestorCancelled ? false : event.isPartial, + isCancelled: event.isCancelled === true ? true : event.isPartial && ancestorCancelled, + startTime: getDateFromNanoseconds(event.startTime), + level: event.level, + events: event.events, + environmentType: event.environmentType, + }, + }; + + return span; +} + +function walkSpanAncestors( + events: TaskEvent[], + event: PreparedEvent, + callback: (event: PreparedEvent, level: number) => { stop: boolean } +) { + const parentId = event.parentId; + if (!parentId) { + return; + } + + let parentEvent = getSpanEvent(events, parentId); + let level = 1; + + while (parentEvent) { + const preparedParentEvent = prepareEvent(parentEvent); + + const result = callback(preparedParentEvent, level); + + if (result.stop) { + return; + } + + if (!preparedParentEvent.parentId) { + return; + } + + parentEvent = getSpanEvent(events, preparedParentEvent.parentId); + + level++; + } +} + +function getSpanEvent(events: TaskEvent[], spanId: string) { + const spans = events.filter((e) => e.spanId === spanId); + const completedSpan = spans.find((s) => !s.isPartial); + + if (completedSpan) { + return completedSpan; + } + + return spans.at(0); +} + +export function prepareEvent(event: TaskEvent): PreparedEvent { + return { + ...event, + duration: Number(event.duration), + events: parseEventsField(event.events), + style: parseStyleField(event.style), + }; +} + +function parseEventsField(events: Prisma.JsonValue): SpanEvents { + const unsafe = events + ? (events as any[]).map((e) => ({ + ...e, + properties: unflattenAttributes(e.properties as Attributes), + })) + : undefined; + + return unsafe as SpanEvents; +} + +function parseStyleField(style: Prisma.JsonValue): TaskEventStyle { + const unsafe = unflattenAttributes(style as Attributes); + + if (!unsafe) { + return {}; + } + + if (typeof unsafe === "object") { + return Object.assign( + { + icon: undefined, + variant: undefined, + }, + unsafe + ) as TaskEventStyle; + } + + return {}; +} + +export function isAncestorCancelled(events: Map, spanId: string) { + const event = events.get(spanId); + + if (!event) { + return false; + } + + if (event.isCancelled) { + return true; + } + + if (event.parentId) { + return isAncestorCancelled(events, event.parentId); + } + + return false; +} + +function calculateDurationIfAncestorIsCancelled( + events: Map, + spanId: string, + defaultDuration: number +) { + const event = events.get(spanId); + + if (!event) { + return defaultDuration; + } + + if (event.isCancelled) { + return defaultDuration; + } + + if (!event.isPartial) { + return defaultDuration; + } + + if (event.parentId) { + const cancelledAncestor = findFirstCancelledAncestor(events, event.parentId); + + if (cancelledAncestor) { + // We need to get the cancellation time from the cancellation span event + const cancellationEvent = cancelledAncestor.events.find( + (event) => event.name === "cancellation" + ); + + if (cancellationEvent) { + return calculateDurationFromStart(event.startTime, cancellationEvent.time); + } + } + } + + return defaultDuration; +} + +export function calculateDurationFromStart(startTime: bigint, endTime: Date = new Date()) { + const $endtime = typeof endTime === "string" ? new Date(endTime) : endTime; + + return Number(BigInt($endtime.getTime() * 1_000_000) - startTime); +} + +function findFirstCancelledAncestor(events: Map, spanId: string) { + const event = events.get(spanId); + + if (!event) { + return; + } + + if (event.isCancelled) { + return event; + } + + if (event.parentId) { + return findFirstCancelledAncestor(events, event.parentId); + } + + return; +} + +export function getDateFromNanoseconds(nanoseconds: bigint) { + return new Date(Number(nanoseconds) / 1_000_000); +} + +export function getNowInNanoseconds(): bigint { + return BigInt(new Date().getTime() * 1_000_000); +} + +export function rehydrateJson(json: Prisma.JsonValue): any { + if (json === null) { + return undefined; + } + + if (json === NULL_SENTINEL) { + return null; + } + + if (typeof json === "string") { + return json; + } + + if (typeof json === "number") { + return json; + } + + if (typeof json === "boolean") { + return json; + } + + if (Array.isArray(json)) { + return json.map((item) => rehydrateJson(item)); + } + + if (typeof json === "object") { + return unflattenAttributes(json as Attributes); + } + + return null; +} + +export function rehydrateShow(properties: Prisma.JsonValue): { actions?: boolean } | undefined { + if (properties === null || properties === undefined) { + return; + } + + if (typeof properties !== "object") { + return; + } + + if (Array.isArray(properties)) { + return; + } + + const actions = properties[SemanticInternalAttributes.SHOW_ACTIONS]; + + if (typeof actions === "boolean") { + return { actions }; + } + + return; +} + +function sanitizedAttributes(json: Prisma.JsonValue) { + if (json === null || json === undefined) { + return; + } + + const withoutPrivateProperties = removePrivateProperties(json as Attributes); + if (!withoutPrivateProperties) { + return; + } + + return unflattenAttributes(withoutPrivateProperties); +} +// removes keys that start with a $ sign. If there are no keys left, return undefined +function removePrivateProperties( + attributes: Attributes | undefined | null +): Attributes | undefined { + if (!attributes) { + return undefined; + } + + const result: Attributes = {}; + + for (const [key, value] of Object.entries(attributes)) { + if (key.startsWith("$")) { + continue; + } + + result[key] = value; + } + + if (Object.keys(result).length === 0) { + return undefined; + } + + return result; +} + +function transformEvents(events: SpanEvents, properties: Attributes, isDev: boolean): SpanEvents { + return (events ?? []).map((event) => transformEvent(event, properties, isDev)); +} + +function transformEvent(event: SpanEvent, properties: Attributes, isDev: boolean): SpanEvent { + if (isExceptionSpanEvent(event)) { + return { + ...event, + properties: { + exception: transformException(event.properties.exception, properties, isDev), + }, + }; + } + + return event; +} + +function transformException( + exception: ExceptionEventProperties, + properties: Attributes, + isDev: boolean +): ExceptionEventProperties { + const projectDirAttributeValue = properties[SemanticInternalAttributes.PROJECT_DIR]; + + if (projectDirAttributeValue !== undefined && typeof projectDirAttributeValue !== "string") { + return exception; + } + + return { + ...exception, + stacktrace: exception.stacktrace + ? correctErrorStackTrace(exception.stacktrace, projectDirAttributeValue, { + removeFirstLine: true, + isDev, + }) + : undefined, + }; +} diff --git a/apps/webapp/app/v3/eventRepository.server.ts b/apps/webapp/app/v3/eventRepository.server.ts index 989ba2b27..03b76b119 100644 --- a/apps/webapp/app/v3/eventRepository.server.ts +++ b/apps/webapp/app/v3/eventRepository.server.ts @@ -32,6 +32,16 @@ import { logger } from "~/services/logger.server"; import { singleton } from "~/utils/singleton"; import { DynamicFlushScheduler } from "./dynamicFlushScheduler.server"; import { startActiveSpan } from "./tracer.server"; +import { + calculateDurationFromStart, + getDateFromNanoseconds, + getNowInNanoseconds, + isAncestorCancelled, + prepareEvent, + prepareTrace, + rehydrateJson, + rehydrateShow, +} from "~/utils/taskEvent"; const MAX_FLUSH_DEPTH = 5; @@ -387,23 +397,6 @@ export class EventRepository { public async getTraceSummary(traceId: string): Promise { return await startActiveSpan("getTraceSummary", async (span) => { const events = await this.readReplica.taskEvent.findMany({ - select: { - id: true, - spanId: true, - parentId: true, - runId: true, - idempotencyKey: true, - message: true, - style: true, - startTime: true, - duration: true, - isError: true, - isPartial: true, - isCancelled: true, - level: true, - events: true, - environmentType: true, - }, where: { traceId, }, @@ -413,106 +406,13 @@ export class EventRepository { take: env.MAXIMUM_TRACE_SUMMARY_VIEW_COUNT, }); - let preparedEvents: Array = []; - let rootSpanId: string | undefined; - const eventsBySpanId = new Map(); - - for (const event of events) { - preparedEvents.push(prepareEvent(event)); - - if (!rootSpanId && !event.parentId) { - rootSpanId = event.spanId; - } - } - - for (const event of preparedEvents) { - const existingEvent = eventsBySpanId.get(event.spanId); - - if (!existingEvent) { - eventsBySpanId.set(event.spanId, event); - continue; - } - - if (event.isCancelled || !event.isPartial) { - eventsBySpanId.set(event.spanId, event); - } - } - - preparedEvents = Array.from(eventsBySpanId.values()); - - const spansBySpanId = new Map(); - - const spans = preparedEvents.map((event) => { - const ancestorCancelled = isAncestorCancelled(eventsBySpanId, event.spanId); - const duration = calculateDurationIfAncestorIsCancelled( - eventsBySpanId, - event.spanId, - event.duration - ); - - const span = { - recordId: event.id, - id: event.spanId, - parentId: event.parentId ?? undefined, - runId: event.runId, - idempotencyKey: event.idempotencyKey, - data: { - message: event.message, - style: event.style, - duration, - isError: event.isError, - isPartial: ancestorCancelled ? false : event.isPartial, - isCancelled: event.isCancelled === true ? true : event.isPartial && ancestorCancelled, - startTime: getDateFromNanoseconds(event.startTime), - level: event.level, - events: event.events, - environmentType: event.environmentType, - }, - }; - - spansBySpanId.set(event.spanId, span); - - return span; - }); - - if (!rootSpanId) { - return; - } - - const rootSpan = spansBySpanId.get(rootSpanId); - - if (!rootSpan) { - return; - } - - return { - rootSpan, - spans, - }; + return prepareTrace(events); }); } public async getRunEvents(runId: string): Promise { return await startActiveSpan("getRunEvents", async (span) => { const events = await this.readReplica.taskEvent.findMany({ - select: { - id: true, - spanId: true, - parentId: true, - runId: true, - idempotencyKey: true, - message: true, - style: true, - startTime: true, - duration: true, - isError: true, - isPartial: true, - isCancelled: true, - level: true, - events: true, - environmentType: true, - taskSlug: true, - }, where: { runId, isPartial: false, @@ -532,221 +432,6 @@ export class EventRepository { }); } - // A Span can be cancelled if it is partial and has a parent that is cancelled - // And a span's duration, if it is partial and has a cancelled parent, is the time between the start of the span and the time of the cancellation event of the parent - public async getSpan(spanId: string, traceId: string) { - return await startActiveSpan("getSpan", async (s) => { - const spanEvent = await this.#getSpanEvent(spanId); - - if (!spanEvent) { - return; - } - - const preparedEvent = prepareEvent(spanEvent); - - const span = await this.#createSpanFromEvent(preparedEvent); - - const output = rehydrateJson(spanEvent.output); - const payload = rehydrateJson(spanEvent.payload); - - const show = rehydrateShow(spanEvent.properties); - - const properties = sanitizedAttributes(spanEvent.properties); - - const messagingEvent = SpanMessagingEvent.optional().safeParse( - (properties as any)?.messaging - ); - - const links: SpanLink[] = []; - - if (messagingEvent.success && messagingEvent.data) { - if (messagingEvent.data.message && "id" in messagingEvent.data.message) { - if (messagingEvent.data.message.id.startsWith("run_")) { - links.push({ - type: "run", - icon: "runs", - title: `Run ${messagingEvent.data.message.id}`, - runId: messagingEvent.data.message.id, - }); - } - } - } - - const backLinks = spanEvent.links as any as Link[] | undefined; - - if (backLinks && backLinks.length > 0) { - backLinks.forEach((l) => { - const title = String( - l.attributes?.[SemanticInternalAttributes.LINK_TITLE] ?? "Triggered by" - ); - - links.push({ - type: "span", - icon: "trigger", - title, - traceId: l.context.traceId, - spanId: l.context.spanId, - }); - }); - } - - const spanEvents = transformEvents( - preparedEvent.events, - spanEvent.metadata as Attributes, - spanEvent.environmentType === "DEVELOPMENT" - ); - - return { - ...spanEvent, - ...span.data, - payload, - output, - properties, - events: spanEvents, - show, - links, - }; - }); - } - - async #createSpanFromEvent(event: PreparedEvent) { - return await startActiveSpan("createSpanFromEvent", async (s) => { - let ancestorCancelled = false; - let duration = event.duration; - - if (!event.isCancelled && event.isPartial) { - await this.#walkSpanAncestors(event, (ancestorEvent, level) => { - if (level >= 8) { - return { stop: true }; - } - - if (ancestorEvent.isCancelled) { - ancestorCancelled = true; - - // We need to get the cancellation time from the cancellation span event - const cancellationEvent = ancestorEvent.events.find( - (event) => event.name === "cancellation" - ); - - if (cancellationEvent) { - duration = calculateDurationFromStart(event.startTime, cancellationEvent.time); - } - - return { stop: true }; - } - - return { stop: false }; - }); - } - - const span = { - recordId: event.id, - id: event.spanId, - parentId: event.parentId ?? undefined, - runId: event.runId, - idempotencyKey: event.idempotencyKey, - data: { - message: event.message, - style: event.style, - duration, - isError: event.isError, - isPartial: ancestorCancelled ? false : event.isPartial, - isCancelled: event.isCancelled === true ? true : event.isPartial && ancestorCancelled, - startTime: getDateFromNanoseconds(event.startTime), - level: event.level, - events: event.events, - environmentType: event.environmentType, - }, - }; - - return span; - }); - } - - async #walkSpanAncestors( - event: PreparedEvent, - callback: (event: PreparedEvent, level: number) => { stop: boolean } - ) { - const parentId = event.parentId; - if (!parentId) { - return; - } - - await startActiveSpan("walkSpanAncestors", async (s) => { - let parentEvent = await this.#getSpanEvent(parentId); - let level = 1; - - while (parentEvent) { - const preparedParentEvent = prepareEvent(parentEvent); - - const result = callback(preparedParentEvent, level); - - if (result.stop) { - return; - } - - if (!preparedParentEvent.parentId) { - return; - } - - parentEvent = await this.#getSpanEvent(preparedParentEvent.parentId); - - level++; - } - }); - } - - async #getSpanAncestors(event: PreparedEvent, levels = 1): Promise> { - if (levels >= 8) { - return []; - } - - if (!event.parentId) { - return []; - } - - const parentEvent = await this.#getSpanEvent(event.parentId); - - if (!parentEvent) { - return []; - } - - const preparedParentEvent = prepareEvent(parentEvent); - - if (!preparedParentEvent.parentId) { - return [preparedParentEvent]; - } - - const moreAncestors = await this.#getSpanAncestors(preparedParentEvent, levels + 1); - - return [preparedParentEvent, ...moreAncestors]; - } - - async #getSpanEvent(spanId: string) { - return await startActiveSpan("getSpanEvent", async (s) => { - const events = await this.readReplica.taskEvent.findMany({ - where: { - spanId, - }, - orderBy: { - startTime: "asc", - }, - }); - - let finalEvent: TaskEvent | undefined; - - for (const event of events) { - if (event.isPartial && finalEvent) { - continue; - } - - finalEvent = event; - } - - return finalEvent; - }); - } - public async recordEvent(message: string, options: TraceEventOptions) { const propagatedContext = extractContextFromCarrier(options.context ?? {}); @@ -1240,119 +925,6 @@ function parseTraceparent(traceparent?: string): { traceId: string; spanId: stri return { traceId, spanId }; } -function prepareEvent(event: QueriedEvent): PreparedEvent { - return { - ...event, - duration: Number(event.duration), - events: parseEventsField(event.events), - style: parseStyleField(event.style), - }; -} - -function parseEventsField(events: Prisma.JsonValue): SpanEvents { - const unsafe = events - ? (events as any[]).map((e) => ({ - ...e, - properties: unflattenAttributes(e.properties as Attributes), - })) - : undefined; - - return unsafe as SpanEvents; -} - -function parseStyleField(style: Prisma.JsonValue): TaskEventStyle { - const unsafe = unflattenAttributes(style as Attributes); - - if (!unsafe) { - return {}; - } - - if (typeof unsafe === "object") { - return Object.assign( - { - icon: undefined, - variant: undefined, - }, - unsafe - ) as TaskEventStyle; - } - - return {}; -} - -function isAncestorCancelled(events: Map, spanId: string) { - const event = events.get(spanId); - - if (!event) { - return false; - } - - if (event.isCancelled) { - return true; - } - - if (event.parentId) { - return isAncestorCancelled(events, event.parentId); - } - - return false; -} - -function calculateDurationIfAncestorIsCancelled( - events: Map, - spanId: string, - defaultDuration: number -) { - const event = events.get(spanId); - - if (!event) { - return defaultDuration; - } - - if (event.isCancelled) { - return defaultDuration; - } - - if (!event.isPartial) { - return defaultDuration; - } - - if (event.parentId) { - const cancelledAncestor = findFirstCancelledAncestor(events, event.parentId); - - if (cancelledAncestor) { - // We need to get the cancellation time from the cancellation span event - const cancellationEvent = cancelledAncestor.events.find( - (event) => event.name === "cancellation" - ); - - if (cancellationEvent) { - return calculateDurationFromStart(event.startTime, cancellationEvent.time); - } - } - } - - return defaultDuration; -} - -function findFirstCancelledAncestor(events: Map, spanId: string) { - const event = events.get(spanId); - - if (!event) { - return; - } - - if (event.isCancelled) { - return event; - } - - if (event.parentId) { - return findFirstCancelledAncestor(events, event.parentId); - } - - return; -} - // Prioritize spans with the same id, keeping the completed spans over partial spans // Completed spans are either !isPartial or isCancelled function removeDuplicateEvents(events: PreparedEvent[]) { @@ -1382,82 +954,6 @@ function isEmptyJson(json: Prisma.JsonValue) { return false; } -function sanitizedAttributes(json: Prisma.JsonValue) { - if (json === null || json === undefined) { - return; - } - - const withoutPrivateProperties = removePrivateProperties(json as Attributes); - if (!withoutPrivateProperties) { - return; - } - - return unflattenAttributes(withoutPrivateProperties); -} -// removes keys that start with a $ sign. If there are no keys left, return undefined -function removePrivateProperties( - attributes: Attributes | undefined | null -): Attributes | undefined { - if (!attributes) { - return undefined; - } - - const result: Attributes = {}; - - for (const [key, value] of Object.entries(attributes)) { - if (key.startsWith("$")) { - continue; - } - - result[key] = value; - } - - if (Object.keys(result).length === 0) { - return undefined; - } - - return result; -} - -function transformEvents(events: SpanEvents, properties: Attributes, isDev: boolean): SpanEvents { - return (events ?? []).map((event) => transformEvent(event, properties, isDev)); -} - -function transformEvent(event: SpanEvent, properties: Attributes, isDev: boolean): SpanEvent { - if (isExceptionSpanEvent(event)) { - return { - ...event, - properties: { - exception: transformException(event.properties.exception, properties, isDev), - }, - }; - } - - return event; -} - -function transformException( - exception: ExceptionEventProperties, - properties: Attributes, - isDev: boolean -): ExceptionEventProperties { - const projectDirAttributeValue = properties[SemanticInternalAttributes.PROJECT_DIR]; - - if (projectDirAttributeValue !== undefined && typeof projectDirAttributeValue !== "string") { - return exception; - } - - return { - ...exception, - stacktrace: exception.stacktrace - ? correctErrorStackTrace(exception.stacktrace, projectDirAttributeValue, { - removeFirstLine: true, - isDev, - }) - : undefined, - }; -} - function filteredAttributes(attributes: Attributes, prefix: string): Attributes { const result: Attributes = {}; @@ -1469,71 +965,3 @@ function filteredAttributes(attributes: Attributes, prefix: string): Attributes return result; } - -function calculateDurationFromStart(startTime: bigint, endTime: Date = new Date()) { - const $endtime = typeof endTime === "string" ? new Date(endTime) : endTime; - - return Number(BigInt($endtime.getTime() * 1_000_000) - startTime); -} - -function getNowInNanoseconds(): bigint { - return BigInt(new Date().getTime() * 1_000_000); -} - -export function getDateFromNanoseconds(nanoseconds: bigint) { - return new Date(Number(nanoseconds) / 1_000_000); -} - -function rehydrateJson(json: Prisma.JsonValue): any { - if (json === null) { - return undefined; - } - - if (json === NULL_SENTINEL) { - return null; - } - - if (typeof json === "string") { - return json; - } - - if (typeof json === "number") { - return json; - } - - if (typeof json === "boolean") { - return json; - } - - if (Array.isArray(json)) { - return json.map((item) => rehydrateJson(item)); - } - - if (typeof json === "object") { - return unflattenAttributes(json as Attributes); - } - - return null; -} - -function rehydrateShow(properties: Prisma.JsonValue): { actions?: boolean } | undefined { - if (properties === null || properties === undefined) { - return; - } - - if (typeof properties !== "object") { - return; - } - - if (Array.isArray(properties)) { - return; - } - - const actions = properties[SemanticInternalAttributes.SHOW_ACTIONS]; - - if (typeof actions === "boolean") { - return { actions }; - } - - return; -} diff --git a/apps/webapp/package.json b/apps/webapp/package.json index 64efcde7d..69bb14fb5 100644 --- a/apps/webapp/package.json +++ b/apps/webapp/package.json @@ -46,6 +46,7 @@ "@conform-to/react": "^0.6.1", "@conform-to/zod": "^0.6.1", "@depot/sdk-node": "^0.5.0", + "@electric-sql/react": "^0.3.3", "@headlessui/react": "^1.7.8", "@heroicons/react": "^2.0.12", "@internationalized/date": "^3.5.1", @@ -107,6 +108,7 @@ "class-variance-authority": "^0.5.2", "clsx": "^1.2.1", "compression": "^1.7.4", + "cookie": "^0.6.0", "cron-parser": "^4.9.0", "cronstrue": "^2.21.0", "cross-env": "^7.0.3", @@ -150,6 +152,7 @@ "react-resizable-panels": "^2.0.9", "react-stately": "^3.29.1", "react-use": "^17.4.0", + "react-window-splitter": "^0.4.1", "recharts": "^2.12.6", "regression": "^2.0.1", "remix-auth": "^3.6.0", @@ -190,6 +193,7 @@ "@total-typescript/ts-reset": "^0.4.2", "@types/bcryptjs": "^2.4.2", "@types/compression": "^1.7.2", + "@types/cookie": "^0.6.0", "@types/eslint": "^8.4.6", "@types/express": "^4.17.13", "@types/humanize-duration": "^3.27.1", @@ -243,4 +247,4 @@ "engines": { "node": ">=16.0.0" } -} \ No newline at end of file +} diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 1e4950452..07bee8220 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -24,6 +24,11 @@ services: - app_network ports: - 5432:5432 + command: + - -c + - listen_addresses=* + - -c + - wal_level=logical pgadmin: container_name: pgadmin @@ -53,3 +58,15 @@ services: - app_network ports: - 6379:6379 + + electric: + image: electricsql/electric + restart: always + environment: + DATABASE_URL: postgresql://postgres:postgres@database:5432/postgres + networks: + - app_network + ports: + - "3060:3000" + depends_on: + - database diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 650b6b11f..b86e337ce 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -231,6 +231,9 @@ importers: '@depot/sdk-node': specifier: ^0.5.0 version: 0.5.0 + '@electric-sql/react': + specifier: ^0.3.3 + version: 0.3.3(react@18.2.0) '@headlessui/react': specifier: ^1.7.8 version: 1.7.8(react-dom@18.2.0)(react@18.2.0) @@ -414,6 +417,9 @@ importers: compression: specifier: ^1.7.4 version: 1.7.4 + cookie: + specifier: ^0.6.0 + version: 0.6.0 cron-parser: specifier: ^4.9.0 version: 4.9.0 @@ -543,6 +549,9 @@ importers: react-use: specifier: ^17.4.0 version: 17.4.0(react-dom@18.2.0)(react@18.2.0) + react-window-splitter: + specifier: ^0.4.1 + version: 0.4.1(@types/react@18.2.69)(react-dom@18.2.0)(react@18.2.0) recharts: specifier: ^2.12.6 version: 2.12.6(react-dom@18.2.0)(react@18.2.0) @@ -658,6 +667,9 @@ importers: '@types/compression': specifier: ^1.7.2 version: 1.7.2 + '@types/cookie': + specifier: ^0.6.0 + version: 0.6.0 '@types/eslint': specifier: ^8.4.6 version: 8.4.10 @@ -1471,6 +1483,10 @@ packages: resolution: {integrity: sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==} engines: {node: '>=0.10.0'} + /@adobe/css-tools@4.4.0: + resolution: {integrity: sha512-Ff9+ksdQQB3rMncgqDK78uLznstjyfIf2Arnh22pW8kBpLs6rpKDwgnZT46hin5Hl1WzazzK64DOrhSwYpS7bQ==} + dev: false + /@alloc/quick-lru@5.2.0: resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} @@ -4124,6 +4140,25 @@ packages: '@connectrpc/connect-node': 0.13.2(@bufbuild/protobuf@1.7.2) dev: false + /@electric-sql/client@0.3.3: + resolution: {integrity: sha512-fOSTaLZPqa8R9etMm1/rprSIcEQMy1X09XMrn/Yh09rr5z0Da7YdUwjCxiPZD0+B7zkbwi3B7wQS9JFkNugw+Q==} + optionalDependencies: + '@rollup/rollup-darwin-arm64': 4.21.1 + dev: false + + /@electric-sql/react@0.3.3(react@18.2.0): + resolution: {integrity: sha512-oC4Nj7KXxdhLDvTAckB25yy3UsWgUEyNuzqRzcu05yy62fkYgySC3DBMDWwslLblM4ItEG3a+BrJ2JN4hEl3kw==} + peerDependencies: + react: ^18.3.1 + peerDependenciesMeta: + react: + optional: true + dependencies: + '@electric-sql/client': 0.3.3 + react: 18.2.0 + use-sync-external-store: 1.2.2(react@18.2.0) + dev: false + /@emotion/hash@0.9.0: resolution: {integrity: sha512-14FtKiHhy2QoPIzdTcvh//8OyBlknNs2nXRwIhG904opCby3l+9Xaf/wuPvICBF0rc1ZCNBd3nKe9cd2mecVkQ==} dev: true @@ -6345,6 +6380,12 @@ packages: '@swc/helpers': 0.5.2 dev: false + /@internationalized/date@3.5.5: + resolution: {integrity: sha512-H+CfYvOZ0LTJeeLOqm19E3uj/4YjrmOFtBufDHPfvtI80hFAMqtrp7oCACpe4Cil5l8S0Qu/9dYfZc/5lY8WQQ==} + dependencies: + '@swc/helpers': 0.5.2 + dev: false + /@internationalized/message@3.1.1: resolution: {integrity: sha512-ZgHxf5HAPIaR0th+w0RUD62yF6vxitjlprSxmLJ1tam7FOekqRSDELMg4Cr/DdszG5YLsp5BG3FgHgqquQZbqw==} dependencies: @@ -6352,18 +6393,37 @@ packages: intl-messageformat: 10.5.8 dev: false + /@internationalized/message@3.1.4: + resolution: {integrity: sha512-Dygi9hH1s7V9nha07pggCkvmRfDd3q2lWnMGvrJyrOwYMe1yj4D2T9BoH9I6MGR7xz0biQrtLPsqUkqXzIrBOw==} + dependencies: + '@swc/helpers': 0.5.2 + intl-messageformat: 10.5.8 + dev: false + /@internationalized/number@3.5.0: resolution: {integrity: sha512-ZY1BW8HT9WKYvaubbuqXbbDdHhOUMfE2zHHFJeTppid0S+pc8HtdIxFxaYMsGjCb4UsF+MEJ4n2TfU7iHnUK8w==} dependencies: '@swc/helpers': 0.5.2 dev: false + /@internationalized/number@3.5.3: + resolution: {integrity: sha512-rd1wA3ebzlp0Mehj5YTuTI50AQEx80gWFyHcQu+u91/5NgdwBecO8BH6ipPfE+lmQ9d63vpB3H9SHoIUiupllw==} + dependencies: + '@swc/helpers': 0.5.2 + dev: false + /@internationalized/string@3.2.0: resolution: {integrity: sha512-Xx3Sy3f2c9ctT+vh8c7euEaEHQZltp0euZ3Hy4UfT3E13r6lxpUS3kgKyumEjboJZSnaZv7JhqWz3D75v+IxQg==} dependencies: '@swc/helpers': 0.5.2 dev: false + /@internationalized/string@3.2.3: + resolution: {integrity: sha512-9kpfLoA8HegiWTeCbR2livhdVeKobCnVv8tlJ6M2jF+4tcMqDo94ezwlnrUANBWPgd8U7OXIHCk2Ov2qhk4KXw==} + dependencies: + '@swc/helpers': 0.5.2 + dev: false + /@ioredis/commands@1.2.0: resolution: {integrity: sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==} dev: false @@ -8254,6 +8314,19 @@ packages: react: 18.2.0 dev: false + /@radix-ui/react-compose-refs@1.1.0(@types/react@18.2.69)(react@18.2.0): + resolution: {integrity: sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.2.69 + react: 18.2.0 + dev: false + /@radix-ui/react-context@1.0.0(react@18.2.0): resolution: {integrity: sha512-1pVM9RfOQ+n/N5PJK33kRSKsr1glNxomxONs5c49MliinBY6Yw2Q995qfBUUo0/Mbg05B/sGA0gkgPI7kmSHBg==} peerDependencies: @@ -9614,6 +9687,20 @@ packages: '@babel/runtime': 7.24.5 dev: false + /@react-aria/breadcrumbs@3.5.16(react@18.2.0): + resolution: {integrity: sha512-OXLKKu4SmjnSaSHkk4kow5/aH/SzlHWPJt+Uq3xec9TwDOr/Ob8aeFVGFoY0HxfGozuQlUz+4e+d29vfA0jNWg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@react-aria/i18n': 3.12.2(react@18.2.0) + '@react-aria/link': 3.7.4(react@18.2.0) + '@react-aria/utils': 3.25.2(react@18.2.0) + '@react-types/breadcrumbs': 3.7.7(react@18.2.0) + '@react-types/shared': 3.24.1(react@18.2.0) + '@swc/helpers': 0.5.2 + react: 18.2.0 + dev: false + /@react-aria/breadcrumbs@3.5.9(react@18.2.0): resolution: {integrity: sha512-asbXTL5NjeHl1+YIF0K70y8tNHk8Lb6VneYH8yOkpLO49ejyNDYBK0tp0jtI9IZAQiTa2qkhYq58c9LloTwebQ==} peerDependencies: @@ -9643,6 +9730,41 @@ packages: react: 18.2.0 dev: false + /@react-aria/button@3.9.8(react@18.2.0): + resolution: {integrity: sha512-MdbMQ3t5KSCkvKtwYd/Z6sgw0v+r1VQFRYOZ4L53xOkn+u140z8vBpNeWKZh/45gxGv7SJn9s2KstLPdCWmIxw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@react-aria/focus': 3.18.2(react@18.2.0) + '@react-aria/interactions': 3.22.2(react@18.2.0) + '@react-aria/utils': 3.25.2(react@18.2.0) + '@react-stately/toggle': 3.7.7(react@18.2.0) + '@react-types/button': 3.9.6(react@18.2.0) + '@react-types/shared': 3.24.1(react@18.2.0) + '@swc/helpers': 0.5.2 + react: 18.2.0 + dev: false + + /@react-aria/calendar@3.5.11(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-VLhBovLVu3uJXBkHbgEippmo/K58QLcc/tSJQ0aJUNyHsrvPgHEcj484cb+Uj/yOirXEIzaoW6WEvhcdKrb49Q==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@internationalized/date': 3.5.5 + '@react-aria/i18n': 3.12.2(react@18.2.0) + '@react-aria/interactions': 3.22.2(react@18.2.0) + '@react-aria/live-announcer': 3.3.4 + '@react-aria/utils': 3.25.2(react@18.2.0) + '@react-stately/calendar': 3.5.4(react@18.2.0) + '@react-types/button': 3.9.6(react@18.2.0) + '@react-types/calendar': 3.4.9(react@18.2.0) + '@react-types/shared': 3.24.1(react@18.2.0) + '@swc/helpers': 0.5.2 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@react-aria/calendar@3.5.4(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-8k7khgea5kwfWriZJWCADNB0R2d7g5A6tTjUEktK4FFZcTb0RCubFejts4hRyzKlF9XHUro2dfh6sbZrzfMKDQ==} peerDependencies: @@ -9681,6 +9803,50 @@ packages: react: 18.2.0 dev: false + /@react-aria/checkbox@3.14.6(react@18.2.0): + resolution: {integrity: sha512-LICY1PR3WsW/VbuLMjZbxo75+poeo3XCXGcUnk6hxMlWfp/Iy/XHVsHlGu9stRPKRF8BSuOGteaHWVn6IXfwtA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@react-aria/form': 3.0.8(react@18.2.0) + '@react-aria/interactions': 3.22.2(react@18.2.0) + '@react-aria/label': 3.7.11(react@18.2.0) + '@react-aria/toggle': 3.10.7(react@18.2.0) + '@react-aria/utils': 3.25.2(react@18.2.0) + '@react-stately/checkbox': 3.6.8(react@18.2.0) + '@react-stately/form': 3.0.5(react@18.2.0) + '@react-stately/toggle': 3.7.7(react@18.2.0) + '@react-types/checkbox': 3.8.3(react@18.2.0) + '@react-types/shared': 3.24.1(react@18.2.0) + '@swc/helpers': 0.5.2 + react: 18.2.0 + dev: false + + /@react-aria/combobox@3.10.3(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-EdDwr2Rp1xy7yWjOYHt2qF1IpAtUrkaNKZJzlIw1XSwcqizQY6E8orNPdZr6ZwD6/tgujxF1N71JTKyffrR0Xw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@react-aria/i18n': 3.12.2(react@18.2.0) + '@react-aria/listbox': 3.13.3(react-dom@18.2.0)(react@18.2.0) + '@react-aria/live-announcer': 3.3.4 + '@react-aria/menu': 3.15.3(react-dom@18.2.0)(react@18.2.0) + '@react-aria/overlays': 3.23.2(react-dom@18.2.0)(react@18.2.0) + '@react-aria/selection': 3.19.3(react-dom@18.2.0)(react@18.2.0) + '@react-aria/textfield': 3.14.8(react@18.2.0) + '@react-aria/utils': 3.25.2(react@18.2.0) + '@react-stately/collections': 3.10.9(react@18.2.0) + '@react-stately/combobox': 3.9.2(react@18.2.0) + '@react-stately/form': 3.0.5(react@18.2.0) + '@react-types/button': 3.9.6(react@18.2.0) + '@react-types/combobox': 3.12.1(react@18.2.0) + '@react-types/shared': 3.24.1(react@18.2.0) + '@swc/helpers': 0.5.2 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@react-aria/combobox@3.8.2(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-q8Kdw1mx6nSSydXqRagRuyKH1NPGvpSOFjUfgxdO8ZqaEEuZX3ObOoiO/DLtXDndViNc03dMbMpfuJoLYXfCtg==} peerDependencies: @@ -9706,6 +9872,34 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /@react-aria/datepicker@3.11.2(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-6sbLln3VXSBcBRDgSACBzIzF/5KV5NlNOhZvXPFE6KqFw6GbevjZQTv5BNDXiwA3CQoawIRF7zgRvTANw8HkNA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@internationalized/date': 3.5.5 + '@internationalized/number': 3.5.3 + '@internationalized/string': 3.2.3 + '@react-aria/focus': 3.18.2(react@18.2.0) + '@react-aria/form': 3.0.8(react@18.2.0) + '@react-aria/i18n': 3.12.2(react@18.2.0) + '@react-aria/interactions': 3.22.2(react@18.2.0) + '@react-aria/label': 3.7.11(react@18.2.0) + '@react-aria/spinbutton': 3.6.8(react-dom@18.2.0)(react@18.2.0) + '@react-aria/utils': 3.25.2(react@18.2.0) + '@react-stately/datepicker': 3.10.2(react@18.2.0) + '@react-stately/form': 3.0.5(react@18.2.0) + '@react-types/button': 3.9.6(react@18.2.0) + '@react-types/calendar': 3.4.9(react@18.2.0) + '@react-types/datepicker': 3.8.2(react@18.2.0) + '@react-types/dialog': 3.5.12(react@18.2.0) + '@react-types/shared': 3.24.1(react@18.2.0) + '@swc/helpers': 0.5.2 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@react-aria/datepicker@3.9.1(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-bdlY2H/zwe3hQf64Lp1oGTf7Va8ennDyAv4Ffowb+BOoL8+FB9smtGyONKe87zXu7VJL2M5xYAi4n7c004PM+w==} peerDependencies: @@ -9750,6 +9944,22 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /@react-aria/dialog@3.5.17(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-lvfEgaqg922J1hurscqCS600OZQVitGtdpo81kAefJaUzMnCxzrYviyT96aaW0simHOlimbYF5js8lxBLZJRaw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@react-aria/focus': 3.18.2(react@18.2.0) + '@react-aria/overlays': 3.23.2(react-dom@18.2.0)(react@18.2.0) + '@react-aria/utils': 3.25.2(react@18.2.0) + '@react-types/dialog': 3.5.12(react@18.2.0) + '@react-types/shared': 3.24.1(react@18.2.0) + '@swc/helpers': 0.5.2 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@react-aria/dnd@3.5.1(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-7OPGePdle+xNYHAIAUOvIETRMfnkRt7h/C0bCkxUR2GYefEbTzfraso4ppNH2JZ7fCRd0K/Qe+jvQklwusHAKA==} peerDependencies: @@ -9770,6 +9980,26 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /@react-aria/dnd@3.7.2(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-NuE3EGqoBbe9aXAO9mDfbu4kMO7S4MCgkjkCqYi16TWfRUf38ajQbIlqodCx91b3LVN3SYvNbE3D4Tj5ebkljw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@internationalized/string': 3.2.3 + '@react-aria/i18n': 3.12.2(react@18.2.0) + '@react-aria/interactions': 3.22.2(react@18.2.0) + '@react-aria/live-announcer': 3.3.4 + '@react-aria/overlays': 3.23.2(react-dom@18.2.0)(react@18.2.0) + '@react-aria/utils': 3.25.2(react@18.2.0) + '@react-stately/dnd': 3.4.2(react@18.2.0) + '@react-types/button': 3.9.6(react@18.2.0) + '@react-types/shared': 3.24.1(react@18.2.0) + '@swc/helpers': 0.5.2 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@react-aria/focus@3.16.0(react@18.2.0): resolution: {integrity: sha512-GP6EYI07E8NKQQcXHjpIocEU0vh0oi0Vcsd+/71fKS0NnTR0TUOEeil0JuuQ9ymkmPDTu51Aaaa4FxVsuN/23A==} peerDependencies: @@ -9783,6 +10013,19 @@ packages: react: 18.2.0 dev: false + /@react-aria/focus@3.18.2(react@18.2.0): + resolution: {integrity: sha512-Jc/IY+StjA3uqN73o6txKQ527RFU7gnG5crEl5Xy3V+gbYp2O5L3ezAo/E0Ipi2cyMbG6T5Iit1IDs7hcGu8aw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@react-aria/interactions': 3.22.2(react@18.2.0) + '@react-aria/utils': 3.25.2(react@18.2.0) + '@react-types/shared': 3.24.1(react@18.2.0) + '@swc/helpers': 0.5.2 + clsx: 2.1.0 + react: 18.2.0 + dev: false + /@react-aria/form@3.0.1(react@18.2.0): resolution: {integrity: sha512-6586oODMDR4/ciGRwXjpvEAg7tWGSDrXE//waK0n5e5sMuzlPOo1DHc5SpPTvz0XdJsu6VDt2rHdVWVIC9LEyw==} peerDependencies: @@ -9796,6 +10039,42 @@ packages: react: 18.2.0 dev: false + /@react-aria/form@3.0.8(react@18.2.0): + resolution: {integrity: sha512-8S2QiyUdAgK43M3flohI0R+2rTyzH088EmgeRArA8euvJTL16cj/oSOKMEgWVihjotJ9n6awPb43ZhKboyNsMg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@react-aria/interactions': 3.22.2(react@18.2.0) + '@react-aria/utils': 3.25.2(react@18.2.0) + '@react-stately/form': 3.0.5(react@18.2.0) + '@react-types/shared': 3.24.1(react@18.2.0) + '@swc/helpers': 0.5.2 + react: 18.2.0 + dev: false + + /@react-aria/grid@3.10.3(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-l0r9mz05Gwjq3t6JOTNQOf+oAoWN0bXELPJtIr8m0XyXMPFCQe1xsTaX8igVQdrDmXyBc75RAWS0BJo2JF2fIA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@react-aria/focus': 3.18.2(react@18.2.0) + '@react-aria/i18n': 3.12.2(react@18.2.0) + '@react-aria/interactions': 3.22.2(react@18.2.0) + '@react-aria/live-announcer': 3.3.4 + '@react-aria/selection': 3.19.3(react-dom@18.2.0)(react@18.2.0) + '@react-aria/utils': 3.25.2(react@18.2.0) + '@react-stately/collections': 3.10.9(react@18.2.0) + '@react-stately/grid': 3.9.2(react@18.2.0) + '@react-stately/selection': 3.16.2(react@18.2.0) + '@react-types/checkbox': 3.8.3(react@18.2.0) + '@react-types/grid': 3.2.8(react@18.2.0) + '@react-types/shared': 3.24.1(react@18.2.0) + '@swc/helpers': 0.5.2 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@react-aria/grid@3.8.6(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-JlQDkdm5heG1FfRyy5KnB8b6s/hRqSI6Xt2xN2AccLX5kcbfFr2/d5KVxyf6ahfa4Gfd46alN6477ju5eTWJew==} peerDependencies: @@ -9839,6 +10118,27 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /@react-aria/gridlist@3.9.3(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-bb9GnKKeuL6NljoVUcHxr9F0cy/2WDOXRYeMikTnviRw6cuX95oojrhFfCUvz2d6ID22Btrvh7LkE+oIPVuc+g==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@react-aria/focus': 3.18.2(react@18.2.0) + '@react-aria/grid': 3.10.3(react-dom@18.2.0)(react@18.2.0) + '@react-aria/i18n': 3.12.2(react@18.2.0) + '@react-aria/interactions': 3.22.2(react@18.2.0) + '@react-aria/selection': 3.19.3(react-dom@18.2.0)(react@18.2.0) + '@react-aria/utils': 3.25.2(react@18.2.0) + '@react-stately/collections': 3.10.9(react@18.2.0) + '@react-stately/list': 3.10.8(react@18.2.0) + '@react-stately/tree': 3.8.4(react@18.2.0) + '@react-types/shared': 3.24.1(react@18.2.0) + '@swc/helpers': 0.5.2 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@react-aria/i18n@3.10.0(react@18.2.0): resolution: {integrity: sha512-sviD5Y1pLPG49HHRmVjR+5nONrp0HK219+nu9Y7cDfUhXu2EjyhMS9t/n9/VZ69hHChZ2PnHYLEE2visu9CuCg==} peerDependencies: @@ -9855,6 +10155,22 @@ packages: react: 18.2.0 dev: false + /@react-aria/i18n@3.12.2(react@18.2.0): + resolution: {integrity: sha512-PvEyC6JWylTpe8dQEWqQwV6GiA+pbTxHQd//BxtMSapRW3JT9obObAnb/nFhj3HthkUvqHyj0oO1bfeN+mtD8A==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@internationalized/date': 3.5.5 + '@internationalized/message': 3.1.4 + '@internationalized/number': 3.5.3 + '@internationalized/string': 3.2.3 + '@react-aria/ssr': 3.9.5(react@18.2.0) + '@react-aria/utils': 3.25.2(react@18.2.0) + '@react-types/shared': 3.24.1(react@18.2.0) + '@swc/helpers': 0.5.2 + react: 18.2.0 + dev: false + /@react-aria/interactions@3.20.1(react@18.2.0): resolution: {integrity: sha512-PLNBr87+SzRhe9PvvF9qvzYeP4ofTwfKSorwmO+hjr3qoczrSXf4LRQlb27wB6hF10C7ZE/XVbUI1lj4QQrZ/g==} peerDependencies: @@ -9867,6 +10183,29 @@ packages: react: 18.2.0 dev: false + /@react-aria/interactions@3.22.2(react@18.2.0): + resolution: {integrity: sha512-xE/77fRVSlqHp2sfkrMeNLrqf2amF/RyuAS6T5oDJemRSgYM3UoxTbWjucPhfnoW7r32pFPHHgz4lbdX8xqD/g==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@react-aria/ssr': 3.9.5(react@18.2.0) + '@react-aria/utils': 3.25.2(react@18.2.0) + '@react-types/shared': 3.24.1(react@18.2.0) + '@swc/helpers': 0.5.2 + react: 18.2.0 + dev: false + + /@react-aria/label@3.7.11(react@18.2.0): + resolution: {integrity: sha512-REgejE5Qr8cXG/b8H2GhzQmjQlII/0xQW/4eDzydskaTLvA7lF5HoJUE6biYTquH5va38d8XlH465RPk+bvHzA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@react-aria/utils': 3.25.2(react@18.2.0) + '@react-types/shared': 3.24.1(react@18.2.0) + '@swc/helpers': 0.5.2 + react: 18.2.0 + dev: false + /@react-aria/label@3.7.4(react@18.2.0): resolution: {integrity: sha512-3Y0yyrqpLzZdzHw+TOyzwuyx5wa2ujU5DGfKuL5GFnU9Ii4DtdwBGSYS7Yu7qadU+eQmG4OGhAgFVswbIgIwJw==} peerDependencies: @@ -9892,6 +10231,20 @@ packages: react: 18.2.0 dev: false + /@react-aria/link@3.7.4(react@18.2.0): + resolution: {integrity: sha512-E8SLDuS9ssm/d42+3sDFNthfMcNXMUrT2Tq1DIZt22EsMcuEzmJ9B0P7bDP5RgvIw05xVGqZ20nOpU4mKTxQtA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@react-aria/focus': 3.18.2(react@18.2.0) + '@react-aria/interactions': 3.22.2(react@18.2.0) + '@react-aria/utils': 3.25.2(react@18.2.0) + '@react-types/link': 3.5.7(react@18.2.0) + '@react-types/shared': 3.24.1(react@18.2.0) + '@swc/helpers': 0.5.2 + react: 18.2.0 + dev: false + /@react-aria/listbox@3.11.3(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-PBrnldmyEYUUJvfDeljW8ITvZyBTfGpLNf0b5kfBPK3TDgRH4niEH2vYEcaZvSqb0FrpdvcunuTRXcOpfb+gCQ==} peerDependencies: @@ -9911,12 +10264,37 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /@react-aria/listbox@3.13.3(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-htluPyDfFtn66OEYaJdIaFCYH9wGCNk30vOgZrQkPul9F9Cjce52tTyPVR0ERsf14oCUsjjS5qgeq3dGidRqEw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@react-aria/interactions': 3.22.2(react@18.2.0) + '@react-aria/label': 3.7.11(react@18.2.0) + '@react-aria/selection': 3.19.3(react-dom@18.2.0)(react@18.2.0) + '@react-aria/utils': 3.25.2(react@18.2.0) + '@react-stately/collections': 3.10.9(react@18.2.0) + '@react-stately/list': 3.10.8(react@18.2.0) + '@react-types/listbox': 3.5.1(react@18.2.0) + '@react-types/shared': 3.24.1(react@18.2.0) + '@swc/helpers': 0.5.2 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@react-aria/live-announcer@3.3.1: resolution: {integrity: sha512-hsc77U7S16trM86d+peqJCOCQ7/smO1cybgdpOuzXyiwcHQw8RQ4GrXrS37P4Ux/44E9nMZkOwATQRT2aK8+Ew==} dependencies: '@swc/helpers': 0.5.2 dev: false + /@react-aria/live-announcer@3.3.4: + resolution: {integrity: sha512-w8lxs35QrRrn6pBNzVfyGOeqWdxeVKf9U6bXIVwhq7rrTqRULL8jqy8RJIMfIs1s8G5FpwWYjyBOjl2g5Cu1iA==} + dependencies: + '@swc/helpers': 0.5.2 + dev: false + /@react-aria/menu@3.12.0(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-Nsujv3b61WR0gybDKnBjAeyxDVJOfPLMggRUf9SQDfPWnrPXEsAFxaPaVcAkzlfI4HiQs1IxNwsKFNpc3PPZTQ==} peerDependencies: @@ -9940,6 +10318,41 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /@react-aria/menu@3.15.3(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-vvUmVjJwIg3h2r+7isQXTwlmoDlPAFBckHkg94p3afrT1kNOTHveTsaVl17mStx/ymIioaAi3PrIXk/PZXp1jw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@react-aria/focus': 3.18.2(react@18.2.0) + '@react-aria/i18n': 3.12.2(react@18.2.0) + '@react-aria/interactions': 3.22.2(react@18.2.0) + '@react-aria/overlays': 3.23.2(react-dom@18.2.0)(react@18.2.0) + '@react-aria/selection': 3.19.3(react-dom@18.2.0)(react@18.2.0) + '@react-aria/utils': 3.25.2(react@18.2.0) + '@react-stately/collections': 3.10.9(react@18.2.0) + '@react-stately/menu': 3.8.2(react@18.2.0) + '@react-stately/tree': 3.8.4(react@18.2.0) + '@react-types/button': 3.9.6(react@18.2.0) + '@react-types/menu': 3.9.11(react@18.2.0) + '@react-types/shared': 3.24.1(react@18.2.0) + '@swc/helpers': 0.5.2 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + + /@react-aria/meter@3.4.16(react@18.2.0): + resolution: {integrity: sha512-hJqKnEE6mmK2Psx5kcI7NZ44OfTg0Bp7DatQSQ4zZE4yhnykRRwxqSKjze37tPR63cCqgRXtQ5LISfBfG54c0Q==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@react-aria/progress': 3.4.16(react@18.2.0) + '@react-types/meter': 3.4.3(react@18.2.0) + '@react-types/shared': 3.24.1(react@18.2.0) + '@swc/helpers': 0.5.2 + react: 18.2.0 + dev: false + /@react-aria/meter@3.4.9(react@18.2.0): resolution: {integrity: sha512-1/FHFmFmSyfQBJ2oH152lp4nps76v1UdhnFbIsmRIH+0g0IfMv1yDT2M9dIZ/b9DgVZSx527FmWOXm0eHGKD6w==} peerDependencies: @@ -9973,6 +10386,27 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /@react-aria/numberfield@3.11.6(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-nvEWiQcWRwj6O2JXmkXEeWoBX/GVZT9zumFJcew3XknGTWJUr3h2AOymIQFt9g4mpag8IgOFEpSIlwhtZHdp1A==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@react-aria/i18n': 3.12.2(react@18.2.0) + '@react-aria/interactions': 3.22.2(react@18.2.0) + '@react-aria/spinbutton': 3.6.8(react-dom@18.2.0)(react@18.2.0) + '@react-aria/textfield': 3.14.8(react@18.2.0) + '@react-aria/utils': 3.25.2(react@18.2.0) + '@react-stately/form': 3.0.5(react@18.2.0) + '@react-stately/numberfield': 3.9.6(react@18.2.0) + '@react-types/button': 3.9.6(react@18.2.0) + '@react-types/numberfield': 3.8.5(react@18.2.0) + '@react-types/shared': 3.24.1(react@18.2.0) + '@swc/helpers': 0.5.2 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@react-aria/overlays@3.20.0(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-2m7MpRJL5UucbEuu08lMHsiFJoDowkJV4JAIFBZYK1NzVH0vF/A+w9HRNM7jRwx2DUxE+iIsZnl8yKV/7KY8OQ==} peerDependencies: @@ -9994,6 +10428,41 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /@react-aria/overlays@3.23.2(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-vjlplr953YAuJfHiP4O+CyrTlr6OaFgXAGrzWq4MVMjnpV/PT5VRJWYFHR0sUGlHTPqeKS4NZbi/xCSgl/3pGQ==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@react-aria/focus': 3.18.2(react@18.2.0) + '@react-aria/i18n': 3.12.2(react@18.2.0) + '@react-aria/interactions': 3.22.2(react@18.2.0) + '@react-aria/ssr': 3.9.5(react@18.2.0) + '@react-aria/utils': 3.25.2(react@18.2.0) + '@react-aria/visually-hidden': 3.8.15(react@18.2.0) + '@react-stately/overlays': 3.6.10(react@18.2.0) + '@react-types/button': 3.9.6(react@18.2.0) + '@react-types/overlays': 3.8.9(react@18.2.0) + '@react-types/shared': 3.24.1(react@18.2.0) + '@swc/helpers': 0.5.2 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + + /@react-aria/progress@3.4.16(react@18.2.0): + resolution: {integrity: sha512-RbDIFQg4+/LG+KYZeLAijt2zH7K2Gp0CY9RKWdho3nU5l3/w57Fa7NrfDGWtpImrt7bR2nRmXMA6ESfr7THfrg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@react-aria/i18n': 3.12.2(react@18.2.0) + '@react-aria/label': 3.7.11(react@18.2.0) + '@react-aria/utils': 3.25.2(react@18.2.0) + '@react-types/progress': 3.5.6(react@18.2.0) + '@react-types/shared': 3.24.1(react@18.2.0) + '@swc/helpers': 0.5.2 + react: 18.2.0 + dev: false + /@react-aria/progress@3.4.9(react@18.2.0): resolution: {integrity: sha512-CME1ZLsJHOmSgK8IAPOC/+vYO5Oc614mkEw5MluT/yclw5rMyjAkK1XsHLjEXy81uwPeiRyoQQIMPKG2/sMxFQ==} peerDependencies: @@ -10026,6 +10495,24 @@ packages: react: 18.2.0 dev: false + /@react-aria/radio@3.10.7(react@18.2.0): + resolution: {integrity: sha512-o2tqIe7xd1y4HeCBQfz/sXIwLJuI6LQbVoCQ1hgk/5dGhQ0LiuXohRYitGRl9zvxW8jYdgLULmOEDt24IflE8A==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@react-aria/focus': 3.18.2(react@18.2.0) + '@react-aria/form': 3.0.8(react@18.2.0) + '@react-aria/i18n': 3.12.2(react@18.2.0) + '@react-aria/interactions': 3.22.2(react@18.2.0) + '@react-aria/label': 3.7.11(react@18.2.0) + '@react-aria/utils': 3.25.2(react@18.2.0) + '@react-stately/radio': 3.10.7(react@18.2.0) + '@react-types/radio': 3.8.3(react@18.2.0) + '@react-types/shared': 3.24.1(react@18.2.0) + '@swc/helpers': 0.5.2 + react: 18.2.0 + dev: false + /@react-aria/searchfield@3.7.1(react@18.2.0): resolution: {integrity: sha512-ebhnV/reNByIZzpcQLHIo1RQ+BrYS8HdwX624i9R7dep1gxGHXYEaqL9aSY+RdngNerB4OeiWmB75em9beSpjQ==} peerDependencies: @@ -10042,6 +10529,22 @@ packages: react: 18.2.0 dev: false + /@react-aria/searchfield@3.7.8(react@18.2.0): + resolution: {integrity: sha512-SsF5xwH8Us548QgzivvbM7nhFbw7pu23xnRRIuhlP3MwOR3jRUFh17NKxf3Z0jvrDv/u0xfm3JKHIgaUN0KJ2A==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@react-aria/i18n': 3.12.2(react@18.2.0) + '@react-aria/textfield': 3.14.8(react@18.2.0) + '@react-aria/utils': 3.25.2(react@18.2.0) + '@react-stately/searchfield': 3.5.6(react@18.2.0) + '@react-types/button': 3.9.6(react@18.2.0) + '@react-types/searchfield': 3.5.8(react@18.2.0) + '@react-types/shared': 3.24.1(react@18.2.0) + '@swc/helpers': 0.5.2 + react: 18.2.0 + dev: false + /@react-aria/select@3.14.1(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-pAy/+Xbj11Lx6bi/O1hWH0NSIDRxFb6V7N0ry2L8x7MALljh516VbpnAc5RgvbjbuKq0cHUAcdINOzOzpYWm4A==} peerDependencies: @@ -10066,6 +10569,30 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /@react-aria/select@3.14.9(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-tiNgMyA2G9nKnFn3pB/lMSgidNToxSFU7r6l4OcG+Vyr63J7B/3dF2lTXq8IYhlfOR3K3uQkjroSx52CmC3NDw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@react-aria/form': 3.0.8(react@18.2.0) + '@react-aria/i18n': 3.12.2(react@18.2.0) + '@react-aria/interactions': 3.22.2(react@18.2.0) + '@react-aria/label': 3.7.11(react@18.2.0) + '@react-aria/listbox': 3.13.3(react-dom@18.2.0)(react@18.2.0) + '@react-aria/menu': 3.15.3(react-dom@18.2.0)(react@18.2.0) + '@react-aria/selection': 3.19.3(react-dom@18.2.0)(react@18.2.0) + '@react-aria/utils': 3.25.2(react@18.2.0) + '@react-aria/visually-hidden': 3.8.15(react@18.2.0) + '@react-stately/select': 3.6.7(react@18.2.0) + '@react-types/button': 3.9.6(react@18.2.0) + '@react-types/select': 3.9.6(react@18.2.0) + '@react-types/shared': 3.24.1(react@18.2.0) + '@swc/helpers': 0.5.2 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@react-aria/selection@3.17.3(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-xl2sgeGH61ngQeE05WOWWPVpGRTPMjQEFmsAWEprArFi4Z7ihSZgpGX22l1w7uSmtXM/eN/v0W8hUYUju5iXlQ==} peerDependencies: @@ -10083,6 +10610,23 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /@react-aria/selection@3.19.3(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-GYoObXCXlmGK08hp7Qfl6Bk0U+bKP5YDWSsX+MzNjJsqzQSLm4S06tRB9ACM7gIo9dDCvL4IRxdSYTJAlJc6bw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@react-aria/focus': 3.18.2(react@18.2.0) + '@react-aria/i18n': 3.12.2(react@18.2.0) + '@react-aria/interactions': 3.22.2(react@18.2.0) + '@react-aria/utils': 3.25.2(react@18.2.0) + '@react-stately/selection': 3.16.2(react@18.2.0) + '@react-types/shared': 3.24.1(react@18.2.0) + '@swc/helpers': 0.5.2 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@react-aria/separator@3.3.9(react@18.2.0): resolution: {integrity: sha512-1wEXiaSJjq2+DR5TC0RKnUBsfZN+YXTzyI7XMzjQoc3YlclumX8wQtzPAOGOEjHB1JKUgo1Gw70FtupVXz58QQ==} peerDependencies: @@ -10094,6 +10638,34 @@ packages: react: 18.2.0 dev: false + /@react-aria/separator@3.4.2(react@18.2.0): + resolution: {integrity: sha512-Xql9Kg3VlGesEUC7QheE+L5b3KgBv0yxiUU+/4JP8V2vfU/XSz4xmprHEeq7KVQVOetn38iiXU8gA5g26SEsUA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@react-aria/utils': 3.25.2(react@18.2.0) + '@react-types/shared': 3.24.1(react@18.2.0) + '@swc/helpers': 0.5.2 + react: 18.2.0 + dev: false + + /@react-aria/slider@3.7.11(react@18.2.0): + resolution: {integrity: sha512-2WAwjANXPsA2LHJ5nxxV4c7ihFAzz2spaBz8+FJ7MDYE7WroYnE8uAXElea1aGo+Lk0DTiAdepLpBkggqPNanw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@react-aria/focus': 3.18.2(react@18.2.0) + '@react-aria/i18n': 3.12.2(react@18.2.0) + '@react-aria/interactions': 3.22.2(react@18.2.0) + '@react-aria/label': 3.7.11(react@18.2.0) + '@react-aria/utils': 3.25.2(react@18.2.0) + '@react-stately/slider': 3.5.7(react@18.2.0) + '@react-types/shared': 3.24.1(react@18.2.0) + '@react-types/slider': 3.7.5(react@18.2.0) + '@swc/helpers': 0.5.2 + react: 18.2.0 + dev: false + /@react-aria/slider@3.7.4(react@18.2.0): resolution: {integrity: sha512-OFJWeGSL2duVDFs/kcjlWsY6bqCVKZgM0aFn2QN4wmID+vfBvBnqGHAgWv3BCePTAPS3+GBjMN002TrftorjwQ==} peerDependencies: @@ -10127,6 +10699,22 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /@react-aria/spinbutton@3.6.8(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-OJMAYRIZ0WrWE+5tZsywrSg4t+aOwl6vl/e1+J64YcGMM+p+AKd61KGG5T0OgNSORXjoVIZOmj6wZ6Od4xfPMw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@react-aria/i18n': 3.12.2(react@18.2.0) + '@react-aria/live-announcer': 3.3.4 + '@react-aria/utils': 3.25.2(react@18.2.0) + '@react-types/button': 3.9.6(react@18.2.0) + '@react-types/shared': 3.24.1(react@18.2.0) + '@swc/helpers': 0.5.2 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@react-aria/ssr@3.9.1(react@18.2.0): resolution: {integrity: sha512-NqzkLFP8ZVI4GSorS0AYljC13QW2sc8bDqJOkBvkAt3M8gbcAXJWVRGtZBCRscki9RZF+rNlnPdg0G0jYkhJcg==} engines: {node: '>= 12'} @@ -10137,6 +10725,16 @@ packages: react: 18.2.0 dev: false + /@react-aria/ssr@3.9.5(react@18.2.0): + resolution: {integrity: sha512-xEwGKoysu+oXulibNUSkXf8itW0npHHTa6c4AyYeZIJyRoegeteYuFpZUBPtIDE8RfHdNsSmE1ssOkxRnwbkuQ==} + engines: {node: '>= 12'} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@swc/helpers': 0.5.2 + react: 18.2.0 + dev: false + /@react-aria/switch@3.6.0(react@18.2.0): resolution: {integrity: sha512-YNWc5fGLNXE4XlmDAKyqAdllRiClGR7ki4KGFY7nL+xR5jxzjCGU3S3ToMK5Op3QSMGZLxY/aYmC4O+MvcoADQ==} peerDependencies: @@ -10149,6 +10747,19 @@ packages: react: 18.2.0 dev: false + /@react-aria/switch@3.6.7(react@18.2.0): + resolution: {integrity: sha512-yBNvKylhc3ZRQ0+7mD0mIenRRe+1yb8YaqMMZr8r3Bf87LaiFtQyhRFziq6ZitcwTJz5LEWjBihxbSVvUrf49w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@react-aria/toggle': 3.10.7(react@18.2.0) + '@react-stately/toggle': 3.7.7(react@18.2.0) + '@react-types/shared': 3.24.1(react@18.2.0) + '@react-types/switch': 3.5.5(react@18.2.0) + '@swc/helpers': 0.5.2 + react: 18.2.0 + dev: false + /@react-aria/table@3.13.3(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-AzmETpyxwNqISTzwHJPs85x9gujG40IIsSOBUdp49oKhB85RbPLvMwhadp4wCVAoHw3erOC/TJxHtVc7o2K1LA==} peerDependencies: @@ -10175,6 +10786,31 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /@react-aria/table@3.15.3(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-nQCLjlEvyJHyuijHw8ESqnA9fxNJfQHx0WPcl08VDEb8VxcE/MVzSAIedSWaqjG5k9Oflz6o/F/zHtzw4AFAow==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@react-aria/focus': 3.18.2(react@18.2.0) + '@react-aria/grid': 3.10.3(react-dom@18.2.0)(react@18.2.0) + '@react-aria/i18n': 3.12.2(react@18.2.0) + '@react-aria/interactions': 3.22.2(react@18.2.0) + '@react-aria/live-announcer': 3.3.4 + '@react-aria/utils': 3.25.2(react@18.2.0) + '@react-aria/visually-hidden': 3.8.15(react@18.2.0) + '@react-stately/collections': 3.10.9(react@18.2.0) + '@react-stately/flags': 3.0.3 + '@react-stately/table': 3.12.2(react@18.2.0) + '@react-types/checkbox': 3.8.3(react@18.2.0) + '@react-types/grid': 3.2.8(react@18.2.0) + '@react-types/shared': 3.24.1(react@18.2.0) + '@react-types/table': 3.10.1(react@18.2.0) + '@swc/helpers': 0.5.2 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@react-aria/tabs@3.8.3(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-Plw0K/5Qv35vYq7pHZFfQB2BF5OClFx4Abzo9hLVx4oMy3qb7i5lxmLBVbt81yPX/MdjYeP4zO1EHGBl4zMRhA==} peerDependencies: @@ -10193,6 +10829,24 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /@react-aria/tabs@3.9.5(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-aQZGAoOIg1B16qlvXIy6+rHbNBNVcWkGjOjeyvqTTPMjXt/FmElkICnqckI7MRJ1lTqzyppCOBitYOHSXRo8Uw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@react-aria/focus': 3.18.2(react@18.2.0) + '@react-aria/i18n': 3.12.2(react@18.2.0) + '@react-aria/selection': 3.19.3(react-dom@18.2.0)(react@18.2.0) + '@react-aria/utils': 3.25.2(react@18.2.0) + '@react-stately/tabs': 3.6.9(react@18.2.0) + '@react-types/shared': 3.24.1(react@18.2.0) + '@react-types/tabs': 3.3.9(react@18.2.0) + '@swc/helpers': 0.5.2 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@react-aria/tag@3.3.1(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-w7d8sVZqxTo8VFfeg2ixLp5kawtrcguGznVY4mt5aE6K8LMJOeNVDqNNfolfyia80VjOWjeX+RpVdVJRdrv/GQ==} peerDependencies: @@ -10213,6 +10867,26 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /@react-aria/tag@3.4.5(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-iyJuATQ8t2cdLC7hiZm143eeZze/MtgxaMq0OewlI9TUje54bkw2Q+CjERdgisIo3Eemf55JJgylGrTcalEJAg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@react-aria/gridlist': 3.9.3(react-dom@18.2.0)(react@18.2.0) + '@react-aria/i18n': 3.12.2(react@18.2.0) + '@react-aria/interactions': 3.22.2(react@18.2.0) + '@react-aria/label': 3.7.11(react@18.2.0) + '@react-aria/selection': 3.19.3(react-dom@18.2.0)(react@18.2.0) + '@react-aria/utils': 3.25.2(react@18.2.0) + '@react-stately/list': 3.10.8(react@18.2.0) + '@react-types/button': 3.9.6(react@18.2.0) + '@react-types/shared': 3.24.1(react@18.2.0) + '@swc/helpers': 0.5.2 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@react-aria/textfield@3.14.1(react@18.2.0): resolution: {integrity: sha512-UMepuYtDdCgrUF4dMphNxrUm23xOmR54aZD1pbp9cJyfioVkJN35BTXZVkD0D07gHLn4RhxKIZxBortQQrLB9g==} peerDependencies: @@ -10230,6 +10904,23 @@ packages: react: 18.2.0 dev: false + /@react-aria/textfield@3.14.8(react@18.2.0): + resolution: {integrity: sha512-FHEvsHdE1cMR2B7rlf+HIneITrC40r201oLYbHAp3q26jH/HUujzFBB9I20qhXjyBohMWfQLqJhSwhs1VW1RJQ==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@react-aria/focus': 3.18.2(react@18.2.0) + '@react-aria/form': 3.0.8(react@18.2.0) + '@react-aria/label': 3.7.11(react@18.2.0) + '@react-aria/utils': 3.25.2(react@18.2.0) + '@react-stately/form': 3.0.5(react@18.2.0) + '@react-stately/utils': 3.10.3(react@18.2.0) + '@react-types/shared': 3.24.1(react@18.2.0) + '@react-types/textfield': 3.9.6(react@18.2.0) + '@swc/helpers': 0.5.2 + react: 18.2.0 + dev: false + /@react-aria/toggle@3.10.0(react@18.2.0): resolution: {integrity: sha512-6cUf4V9TuG2J7AvXUdU/GspEPFCubUOID3mrselSe563RViy+mMZk0vUEOdyoNanDcEXl58W4dE3SGWxFn71vg==} peerDependencies: @@ -10244,6 +10935,21 @@ packages: react: 18.2.0 dev: false + /@react-aria/toggle@3.10.7(react@18.2.0): + resolution: {integrity: sha512-/RJQU8QlPZXRElZ3Tt10F5K5STgUBUGPpfuFUGuwF3Kw3GpPxYsA1YAVjxXz2MMGwS0+y6+U/J1xIs1AF0Jwzg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@react-aria/focus': 3.18.2(react@18.2.0) + '@react-aria/interactions': 3.22.2(react@18.2.0) + '@react-aria/utils': 3.25.2(react@18.2.0) + '@react-stately/toggle': 3.7.7(react@18.2.0) + '@react-types/checkbox': 3.8.3(react@18.2.0) + '@react-types/shared': 3.24.1(react@18.2.0) + '@swc/helpers': 0.5.2 + react: 18.2.0 + dev: false + /@react-aria/tooltip@3.7.0(react@18.2.0): resolution: {integrity: sha512-+u9Sftkfe09IDyPEnbbreFKS50vh9X/WTa7n1u2y3PenI9VreLpUR6czyzda4BlvQ95e9jQz1cVxUjxTNaZmBw==} peerDependencies: @@ -10259,6 +10965,21 @@ packages: react: 18.2.0 dev: false + /@react-aria/tooltip@3.7.7(react@18.2.0): + resolution: {integrity: sha512-UOTTDbbUz7OaE48VjNSWl+XQbYCUs5Gss4I3Tv1pfRLXzVtGYXv3ur/vRayvZR0xd12ANY26fZPNkSmCFpmiXw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@react-aria/focus': 3.18.2(react@18.2.0) + '@react-aria/interactions': 3.22.2(react@18.2.0) + '@react-aria/utils': 3.25.2(react@18.2.0) + '@react-stately/tooltip': 3.4.12(react@18.2.0) + '@react-types/shared': 3.24.1(react@18.2.0) + '@react-types/tooltip': 3.4.11(react@18.2.0) + '@swc/helpers': 0.5.2 + react: 18.2.0 + dev: false + /@react-aria/utils@3.23.0(react@18.2.0): resolution: {integrity: sha512-fJA63/VU4iQNT8WUvrmll3kvToqMurD69CcgVmbQ56V7ZbvlzFi44E7BpnoaofScYLLtFWRjVdaHsohT6O/big==} peerDependencies: @@ -10272,6 +10993,31 @@ packages: react: 18.2.0 dev: false + /@react-aria/utils@3.25.2(react@18.2.0): + resolution: {integrity: sha512-GdIvG8GBJJZygB4L2QJP1Gabyn2mjFsha73I2wSe+o4DYeGWoJiMZRM06PyTIxLH4S7Sn7eVDtsSBfkc2VY/NA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@react-aria/ssr': 3.9.5(react@18.2.0) + '@react-stately/utils': 3.10.3(react@18.2.0) + '@react-types/shared': 3.24.1(react@18.2.0) + '@swc/helpers': 0.5.2 + clsx: 2.1.0 + react: 18.2.0 + dev: false + + /@react-aria/visually-hidden@3.8.15(react@18.2.0): + resolution: {integrity: sha512-l+sJ7xTdD5Sd6+rDNDaeJCSPnHOsI+BaJyApvb/YcVgHa7rB47lp6TXCWUCDItcPY4JqRGyeByRJVrtzBFTWCw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@react-aria/interactions': 3.22.2(react@18.2.0) + '@react-aria/utils': 3.25.2(react@18.2.0) + '@react-types/shared': 3.24.1(react@18.2.0) + '@swc/helpers': 0.5.2 + react: 18.2.0 + dev: false + /@react-aria/visually-hidden@3.8.8(react@18.2.0): resolution: {integrity: sha512-Cn2PYKD4ijGDtF0+dvsh8qa4y7KTNAlkTG6h20r8Q+6UTyRNmtE2/26QEaApRF8CBiNy9/BZC/ZC4FK2OjvCoA==} peerDependencies: @@ -10734,6 +11480,10 @@ packages: react: 18.2.0 dev: false + /@react-spring/rafz@9.7.4: + resolution: {integrity: sha512-mqDI6rW0Ca8IdryOMiXRhMtVGiEGLIO89vIOyFQXRIwwIMX30HLya24g9z4olDvFyeDW3+kibiKwtZnA4xhldA==} + dev: false + /@react-stately/calendar@3.4.3(react@18.2.0): resolution: {integrity: sha512-OrEcdskszDjnjVnFuSiDC2PVBJ6lWMCJROD5s6W1LUehUtBp8LX9wPavAGHV43LbhN9ldj560sxaQ4WCddrRCA==} peerDependencies: @@ -10747,6 +11497,19 @@ packages: react: 18.2.0 dev: false + /@react-stately/calendar@3.5.4(react@18.2.0): + resolution: {integrity: sha512-R2011mtFSXIjzMXaA+CZ1sflPm9XkTBMqVk77Bnxso2ZsG7FUX8nqFmaDavxwTuHFC6OUexAGSMs8bP9KycTNg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@internationalized/date': 3.5.5 + '@react-stately/utils': 3.10.3(react@18.2.0) + '@react-types/calendar': 3.4.9(react@18.2.0) + '@react-types/shared': 3.24.1(react@18.2.0) + '@swc/helpers': 0.5.2 + react: 18.2.0 + dev: false + /@react-stately/checkbox@3.6.1(react@18.2.0): resolution: {integrity: sha512-rOjFeVBy32edYwhKiHj3ZLdLeO+xZ2fnBwxnOBjcygnw4Neygm8FJH/dB1J0hdYYR349yby86ED2x0wRc84zPw==} peerDependencies: @@ -10760,6 +11523,19 @@ packages: react: 18.2.0 dev: false + /@react-stately/checkbox@3.6.8(react@18.2.0): + resolution: {integrity: sha512-c8TWjU67XHHBCpqj6+FXXhQUWGr2Pil1IKggX81pkedhWiJl3/7+WHJuZI0ivGnRjp3aISNOG8UNVlBEjS9E8A==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@react-stately/form': 3.0.5(react@18.2.0) + '@react-stately/utils': 3.10.3(react@18.2.0) + '@react-types/checkbox': 3.8.3(react@18.2.0) + '@react-types/shared': 3.24.1(react@18.2.0) + '@swc/helpers': 0.5.2 + react: 18.2.0 + dev: false + /@react-stately/collections@3.10.4(react@18.2.0): resolution: {integrity: sha512-OHhCrItGt4zB2bSrgObRo0H2SC7QlkH8ReGxo+NVIWchXRLRoiWBP7S+IwleewEo5gOqDVPY3hqA9n4iiI8twg==} peerDependencies: @@ -10770,6 +11546,16 @@ packages: react: 18.2.0 dev: false + /@react-stately/collections@3.10.9(react@18.2.0): + resolution: {integrity: sha512-plyrng6hOQMG8LrjArMA6ts/DgWyXln3g90/hFNbqe/hdVYF53sDVsj8Jb+5LtoYTpiAlV6eOvy1XR0vPZUf8w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@react-types/shared': 3.24.1(react@18.2.0) + '@swc/helpers': 0.5.2 + react: 18.2.0 + dev: false + /@react-stately/combobox@3.8.1(react@18.2.0): resolution: {integrity: sha512-FaWkqTXQdWg7ptaeU4iPcqF/kxbRg2ZNUcvW/hiL/enciV5tRCsddvfNqvDvy1L30z9AUwlp9MWqzm/DhBITCw==} peerDependencies: @@ -10787,6 +11573,23 @@ packages: react: 18.2.0 dev: false + /@react-stately/combobox@3.9.2(react@18.2.0): + resolution: {integrity: sha512-ZsbAcD58IvxZqwYxg9d2gOf8R/k5RUB2TPUiGKD6wgWfEKH6SDzY3bgRByHGOyMCyJB62cHjih/ZShizNTguqA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@react-stately/collections': 3.10.9(react@18.2.0) + '@react-stately/form': 3.0.5(react@18.2.0) + '@react-stately/list': 3.10.8(react@18.2.0) + '@react-stately/overlays': 3.6.10(react@18.2.0) + '@react-stately/select': 3.6.7(react@18.2.0) + '@react-stately/utils': 3.10.3(react@18.2.0) + '@react-types/combobox': 3.12.1(react@18.2.0) + '@react-types/shared': 3.24.1(react@18.2.0) + '@swc/helpers': 0.5.2 + react: 18.2.0 + dev: false + /@react-stately/data@3.11.0(react@18.2.0): resolution: {integrity: sha512-0BlPT58WrAtUvpiEfUuyvIsGFTzp/9vA5y+pk53kGJhOdc5tqBGHi9cg40pYE/i1vdHJGMpyHGRD9nkQb8wN3Q==} peerDependencies: @@ -10797,6 +11600,22 @@ packages: react: 18.2.0 dev: false + /@react-stately/datepicker@3.10.2(react@18.2.0): + resolution: {integrity: sha512-pa5IZUw+49AyOnddwu4XwU2kI5eo/1thbiIVNHP8uDpbbBrBkquSk3zVFDAGX1cu/I1U2VUkt64U/dxgkwaMQw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@internationalized/date': 3.5.5 + '@internationalized/string': 3.2.3 + '@react-stately/form': 3.0.5(react@18.2.0) + '@react-stately/overlays': 3.6.10(react@18.2.0) + '@react-stately/utils': 3.10.3(react@18.2.0) + '@react-types/datepicker': 3.8.2(react@18.2.0) + '@react-types/shared': 3.24.1(react@18.2.0) + '@swc/helpers': 0.5.2 + react: 18.2.0 + dev: false + /@react-stately/datepicker@3.9.1(react@18.2.0): resolution: {integrity: sha512-o5xLvlZGJyAbTev2yruGlV2fzQyIDuYTgL19TTt0W0WCfjGGr/AAA9GjGXXmyoRA7sZMxqIPnnv7lNrdA38ofA==} peerDependencies: @@ -10824,12 +11643,29 @@ packages: react: 18.2.0 dev: false + /@react-stately/dnd@3.4.2(react@18.2.0): + resolution: {integrity: sha512-VrHmNoNdVGrx5JHdz/zewmN+N8rlZe+vL/iAOLmvQ74RRLEz8KDFnHdlhgKg1AZqaSg3JJ18BlHEkS7oL1n+tA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@react-stately/selection': 3.16.2(react@18.2.0) + '@react-types/shared': 3.24.1(react@18.2.0) + '@swc/helpers': 0.5.2 + react: 18.2.0 + dev: false + /@react-stately/flags@3.0.0: resolution: {integrity: sha512-e3i2ItHbIa0eEwmSXAnPdD7K8syW76JjGe8ENxwFJPW/H1Pu9RJfjkCb/Mq0WSPN/TpxBb54+I9TgrGhbCoZ9w==} dependencies: '@swc/helpers': 0.4.14 dev: false + /@react-stately/flags@3.0.3: + resolution: {integrity: sha512-/ha7XFA0RZTQsbzSPwu3KkbNMgbvuM0GuMTYLTBWpgBrovBNTM+QqI/PfZTdHg8PwCYF4H5Y8gjdSpdulCvJFw==} + dependencies: + '@swc/helpers': 0.5.2 + dev: false + /@react-stately/form@3.0.0(react@18.2.0): resolution: {integrity: sha512-C8wkfFmtx1escizibhdka5JvTy9/Vp173CS9cakjvWTmnjYYC1nOlzwp7BsYWTgerCFbRY/BU/Cf/bJDxPiUKQ==} peerDependencies: @@ -10840,6 +11676,16 @@ packages: react: 18.2.0 dev: false + /@react-stately/form@3.0.5(react@18.2.0): + resolution: {integrity: sha512-J3plwJ63HQz109OdmaTqTA8Qhvl3gcYYK7DtgKyNP6mc/Me2Q4tl2avkWoA+22NRuv5m+J8TpBk4AVHUEOwqeQ==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@react-types/shared': 3.24.1(react@18.2.0) + '@swc/helpers': 0.5.2 + react: 18.2.0 + dev: false + /@react-stately/grid@3.8.4(react@18.2.0): resolution: {integrity: sha512-rwqV1K4lVhaiaqJkt4TfYqdJoVIyqvSm98rKAYfCNzrKcivVpoiCMJ2EMt6WlYCjDVBdEOQ7fMV1I60IV0pntA==} peerDependencies: @@ -10853,6 +11699,19 @@ packages: react: 18.2.0 dev: false + /@react-stately/grid@3.9.2(react@18.2.0): + resolution: {integrity: sha512-2gK//sqAqg2Xaq6UITTFQwFUJnBRgcW+cKBVbFt+F8d152xB6UwwTS/K79E5PUkOotwqZgTEpkrSFs/aVxCLpw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@react-stately/collections': 3.10.9(react@18.2.0) + '@react-stately/selection': 3.16.2(react@18.2.0) + '@react-types/grid': 3.2.8(react@18.2.0) + '@react-types/shared': 3.24.1(react@18.2.0) + '@swc/helpers': 0.5.2 + react: 18.2.0 + dev: false + /@react-stately/list@3.10.2(react@18.2.0): resolution: {integrity: sha512-INt+zofkIg2KN8B95xPi9pJG7ZFWAm30oIm/lCPBqM3K1Nm03/QaAbiQj2QeJcOsG3lb7oqI6D6iwTolwJkjIQ==} peerDependencies: @@ -10866,6 +11725,19 @@ packages: react: 18.2.0 dev: false + /@react-stately/list@3.10.8(react@18.2.0): + resolution: {integrity: sha512-rHCiPLXd+Ry3ztR9DkLA5FPQeH4Zd4/oJAEDWJ77W3oBBOdiMp3ZdHDLP7KBRh17XGNLO/QruYoHWAQTPiMF4g==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@react-stately/collections': 3.10.9(react@18.2.0) + '@react-stately/selection': 3.16.2(react@18.2.0) + '@react-stately/utils': 3.10.3(react@18.2.0) + '@react-types/shared': 3.24.1(react@18.2.0) + '@swc/helpers': 0.5.2 + react: 18.2.0 + dev: false + /@react-stately/menu@3.6.0(react@18.2.0): resolution: {integrity: sha512-OB6CjNyfOkAuirqx1oTL8z8epS9WDzLyrXjmRnxdiCU9EgRXLGAQNECuO7VIpl58oDry8tgRJiJ8fn8FivWSQA==} peerDependencies: @@ -10878,6 +11750,18 @@ packages: react: 18.2.0 dev: false + /@react-stately/menu@3.8.2(react@18.2.0): + resolution: {integrity: sha512-lt6hIHmSixMzkKx1rKJf3lbAf01EmEvvIlENL20GLiU9cRbpPnPJ1aJMZ5Ad5ygglA7wAemAx+daPhlTQfF2rg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@react-stately/overlays': 3.6.10(react@18.2.0) + '@react-types/menu': 3.9.11(react@18.2.0) + '@react-types/shared': 3.24.1(react@18.2.0) + '@swc/helpers': 0.5.2 + react: 18.2.0 + dev: false + /@react-stately/numberfield@3.8.0(react@18.2.0): resolution: {integrity: sha512-1XvB8tDOvZKcFnMM6qNLEaTVJcIc0jRFS/9jtS8MzalZvh8DbKi0Ucm1bGU7S5rkCx2QWqZ0rGOIm2h/RlcpkA==} peerDependencies: @@ -10891,6 +11775,30 @@ packages: react: 18.2.0 dev: false + /@react-stately/numberfield@3.9.6(react@18.2.0): + resolution: {integrity: sha512-p2R9admGLI439qZzB39dyANhkruprJJtZwuoGVtxW/VD0ficw6BrPVqAaKG25iwKPkmveleh9p8o+yRqjGedcQ==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@internationalized/number': 3.5.3 + '@react-stately/form': 3.0.5(react@18.2.0) + '@react-stately/utils': 3.10.3(react@18.2.0) + '@react-types/numberfield': 3.8.5(react@18.2.0) + '@swc/helpers': 0.5.2 + react: 18.2.0 + dev: false + + /@react-stately/overlays@3.6.10(react@18.2.0): + resolution: {integrity: sha512-XxZ2qScT5JPwGk9qiVJE4dtVh3AXTcYwGRA5RsHzC26oyVVsegPqY2PmNJGblAh6Q57VyodoVUyebE0Eo5CzRw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@react-stately/utils': 3.10.3(react@18.2.0) + '@react-types/overlays': 3.8.9(react@18.2.0) + '@swc/helpers': 0.5.2 + react: 18.2.0 + dev: false + /@react-stately/overlays@3.6.4(react@18.2.0): resolution: {integrity: sha512-tHEaoAGpE9dSnsskqLPVKum59yGteoSqsniTopodM+miQozbpPlSjdiQnzGLroy5Afx5OZYClE616muNHUILXA==} peerDependencies: @@ -10915,6 +11823,19 @@ packages: react: 18.2.0 dev: false + /@react-stately/radio@3.10.7(react@18.2.0): + resolution: {integrity: sha512-ZwGzFR+sGd42DxRlDTp3G2vLZyhMVtgHkwv2BxazPHxPMvLO9yYl7+3PPNxAmhMB4tg2u9CrzffpGX2rmEJEXA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@react-stately/form': 3.0.5(react@18.2.0) + '@react-stately/utils': 3.10.3(react@18.2.0) + '@react-types/radio': 3.8.3(react@18.2.0) + '@react-types/shared': 3.24.1(react@18.2.0) + '@swc/helpers': 0.5.2 + react: 18.2.0 + dev: false + /@react-stately/searchfield@3.5.0(react@18.2.0): resolution: {integrity: sha512-SStjChkn/33pEn40slKQPnBnmQYyxVazVwPjiBkdeVejC42lUVairUTrGJgF0PNoZTbxn0so2/XzjqTC9T8iCw==} peerDependencies: @@ -10926,6 +11847,17 @@ packages: react: 18.2.0 dev: false + /@react-stately/searchfield@3.5.6(react@18.2.0): + resolution: {integrity: sha512-gVzU0FeWiLYD8VOYRgWlk79Qn7b2eirqOnWhtI5VNuGN8WyNaCIuBp6SkXTW2dY8hs2Hzn8HlMbgy1MIc7130Q==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@react-stately/utils': 3.10.3(react@18.2.0) + '@react-types/searchfield': 3.5.8(react@18.2.0) + '@swc/helpers': 0.5.2 + react: 18.2.0 + dev: false + /@react-stately/select@3.6.1(react@18.2.0): resolution: {integrity: sha512-e5ixtLiYLlFWM8z1msDqXWhflF9esIRfroptZsltMn1lt2iImUlDRlOTZlMtPQzUrDWoiHXRX88sSKUM/jXjQQ==} peerDependencies: @@ -10940,6 +11872,20 @@ packages: react: 18.2.0 dev: false + /@react-stately/select@3.6.7(react@18.2.0): + resolution: {integrity: sha512-hCUIddw0mPxVy1OH6jhyaDwgNea9wESjf+MYdnnTG/abRB+OZv/dWScd87OjzVsHTHWcw7CN4ZzlJoXm0FJbKQ==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@react-stately/form': 3.0.5(react@18.2.0) + '@react-stately/list': 3.10.8(react@18.2.0) + '@react-stately/overlays': 3.6.10(react@18.2.0) + '@react-types/select': 3.9.6(react@18.2.0) + '@react-types/shared': 3.24.1(react@18.2.0) + '@swc/helpers': 0.5.2 + react: 18.2.0 + dev: false + /@react-stately/selection@3.14.2(react@18.2.0): resolution: {integrity: sha512-mL7OoiUgVWaaF7ks5XSxgbXeShijYmD4G3bkBHhqkpugU600QH6BM2hloCq8KOUupk1y8oTljPtF9EmCv375DA==} peerDependencies: @@ -10952,6 +11898,18 @@ packages: react: 18.2.0 dev: false + /@react-stately/selection@3.16.2(react@18.2.0): + resolution: {integrity: sha512-C4eSKw7BIZHJLPzwqGqCnsyFHiUIEyryVQZTJDt6d0wYBOHU6k1pW+Q4VhrZuzSv+IMiI2RkiXeJKc55f0ZXrg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@react-stately/collections': 3.10.9(react@18.2.0) + '@react-stately/utils': 3.10.3(react@18.2.0) + '@react-types/shared': 3.24.1(react@18.2.0) + '@swc/helpers': 0.5.2 + react: 18.2.0 + dev: false + /@react-stately/slider@3.5.0(react@18.2.0): resolution: {integrity: sha512-dOVpIxb7XKuiRxgpHt1bUSlsklciFki100tKIyBPR+Okar9iC/CwLYROYgVfLkGe77jEBNkor9tDLjDGEWcc1w==} peerDependencies: @@ -10964,6 +11922,18 @@ packages: react: 18.2.0 dev: false + /@react-stately/slider@3.5.7(react@18.2.0): + resolution: {integrity: sha512-gEIGTcpBLcXixd8LYiLc8HKrBiGQJltrrEGoOvvTP8KVItXQxmeL+JiSsh8qgOoUdRRpzmAoFNUKGEg2/gtN8A==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@react-stately/utils': 3.10.3(react@18.2.0) + '@react-types/shared': 3.24.1(react@18.2.0) + '@react-types/slider': 3.7.5(react@18.2.0) + '@swc/helpers': 0.5.2 + react: 18.2.0 + dev: false + /@react-stately/table@3.11.4(react@18.2.0): resolution: {integrity: sha512-dWINJIEOKQl4qq3moq+S8xCD3m+yJqBj0dahr+rOkS+t2uqORwzsusTM35D2T/ZHZi49S2GpE7QuDa+edCynPw==} peerDependencies: @@ -10981,6 +11951,23 @@ packages: react: 18.2.0 dev: false + /@react-stately/table@3.12.2(react@18.2.0): + resolution: {integrity: sha512-dUcsrdALylhWz6exqIoqtR/dnrzjIAptMyAUPT378Y/mCYs4PxKkHSvtPEQrZhdQS1ALIIgfeg9KUVIempoXPw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@react-stately/collections': 3.10.9(react@18.2.0) + '@react-stately/flags': 3.0.3 + '@react-stately/grid': 3.9.2(react@18.2.0) + '@react-stately/selection': 3.16.2(react@18.2.0) + '@react-stately/utils': 3.10.3(react@18.2.0) + '@react-types/grid': 3.2.8(react@18.2.0) + '@react-types/shared': 3.24.1(react@18.2.0) + '@react-types/table': 3.10.1(react@18.2.0) + '@swc/helpers': 0.5.2 + react: 18.2.0 + dev: false + /@react-stately/tabs@3.6.3(react@18.2.0): resolution: {integrity: sha512-Nj+Gacwa2SIzYIvHW40GsyX4Q6c8kF7GOuXESeQswbCjnwqhrSbDBp+ngPcUPUJxqFh6JhDCVwAS3wMhUoyUwA==} peerDependencies: @@ -10993,6 +11980,18 @@ packages: react: 18.2.0 dev: false + /@react-stately/tabs@3.6.9(react@18.2.0): + resolution: {integrity: sha512-YZDqZng3HrRX+uXmg6u78x73Oi24G5ICpiXVqDKKDkO333XCA5H8MWItiuPZkYB2h3SbaCaLqSobLkvCoWYpNQ==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@react-stately/list': 3.10.8(react@18.2.0) + '@react-types/shared': 3.24.1(react@18.2.0) + '@react-types/tabs': 3.3.9(react@18.2.0) + '@swc/helpers': 0.5.2 + react: 18.2.0 + dev: false + /@react-stately/toggle@3.7.0(react@18.2.0): resolution: {integrity: sha512-TRksHkCJk/Xogq4181g3CYgJf+EfsJCqX5UZDSw1Z1Kgpvonjmdf6FAfQfCh9QR2OuXUL6hOLUDVLte5OPI+5g==} peerDependencies: @@ -11004,6 +12003,28 @@ packages: react: 18.2.0 dev: false + /@react-stately/toggle@3.7.7(react@18.2.0): + resolution: {integrity: sha512-AS+xB4+hHWa3wzYkbS6pwBkovPfIE02B9SnuYTe0stKcuejpWKo5L3QMptW0ftFYsW3ZPCXuneImfObEw2T01A==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@react-stately/utils': 3.10.3(react@18.2.0) + '@react-types/checkbox': 3.8.3(react@18.2.0) + '@swc/helpers': 0.5.2 + react: 18.2.0 + dev: false + + /@react-stately/tooltip@3.4.12(react@18.2.0): + resolution: {integrity: sha512-QKYT/cze7n9qaBsk7o5ais3jRfhYCzcVRfps+iys/W+/9FFbbhjfQG995Lwi6b+vGOHWfXxXpwmyIO2tzM1Iog==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@react-stately/overlays': 3.6.10(react@18.2.0) + '@react-types/tooltip': 3.4.11(react@18.2.0) + '@swc/helpers': 0.5.2 + react: 18.2.0 + dev: false + /@react-stately/tooltip@3.4.6(react@18.2.0): resolution: {integrity: sha512-uL93bmsXf+OOgpKLPEKfpDH4z+MK2CuqlqVxx7rshN0vjWOSoezE5nzwgee90+RpDrLNNNWTNa7n+NkDRpI1jA==} peerDependencies: @@ -11028,6 +12049,28 @@ packages: react: 18.2.0 dev: false + /@react-stately/tree@3.8.4(react@18.2.0): + resolution: {integrity: sha512-HFNclIXJ/3QdGQWxXbj+tdlmIX/XwCfzAMB5m26xpJ6HtJhia6dtx3GLfcdyHNjmuRbAsTBsAAnnVKBmNRUdIQ==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@react-stately/collections': 3.10.9(react@18.2.0) + '@react-stately/selection': 3.16.2(react@18.2.0) + '@react-stately/utils': 3.10.3(react@18.2.0) + '@react-types/shared': 3.24.1(react@18.2.0) + '@swc/helpers': 0.5.2 + react: 18.2.0 + dev: false + + /@react-stately/utils@3.10.3(react@18.2.0): + resolution: {integrity: sha512-moClv7MlVSHpbYtQIkm0Cx+on8Pgt1XqtPx6fy9rQFb2DNc9u1G3AUVnqA17buOkH1vLxAtX4MedlxMWyRCYYA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@swc/helpers': 0.5.2 + react: 18.2.0 + dev: false + /@react-stately/utils@3.9.0(react@18.2.0): resolution: {integrity: sha512-yPKFY1F88HxuZ15BG2qwAYxtpE4HnIU0Ofi4CuBE0xC6I8mwo4OQjDzi+DZjxQngM9D6AeTTD6F1V8gkozA0Gw==} peerDependencies: @@ -11058,6 +12101,16 @@ packages: react: 18.2.0 dev: false + /@react-types/breadcrumbs@3.7.7(react@18.2.0): + resolution: {integrity: sha512-ZmhXwD2LLzfEA2OvOCp/QvXu8A/Edsrn5q0qUDGsmOZj9SCVeT82bIv8P+mQnATM13mi2gyoik6102Jc1OscJA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@react-types/link': 3.5.7(react@18.2.0) + '@react-types/shared': 3.24.1(react@18.2.0) + react: 18.2.0 + dev: false + /@react-types/button@3.9.1(react@18.2.0): resolution: {integrity: sha512-bf9iTar3PtqnyV9rA+wyFyrskZKhwmOuOd/ifYIjPs56YNVXWH5Wfqj6Dx3xdFBgtKx8mEVQxVhoX+WkHX+rtw==} peerDependencies: @@ -11067,6 +12120,15 @@ packages: react: 18.2.0 dev: false + /@react-types/button@3.9.6(react@18.2.0): + resolution: {integrity: sha512-8lA+D5JLbNyQikf8M/cPP2cji91aVTcqjrGpDqI7sQnaLFikM8eFR6l1ZWGtZS5MCcbfooko77ha35SYplSQvw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@react-types/shared': 3.24.1(react@18.2.0) + react: 18.2.0 + dev: false + /@react-types/calendar@3.4.3(react@18.2.0): resolution: {integrity: sha512-96x57ctX5wNEl+8et3sc2NQm8neOJayEeqOQQpyPtI7jyvst/xBrKCwysf9W/dhgPlUC+KeBAYFWfjd5hFVHYA==} peerDependencies: @@ -11077,6 +12139,16 @@ packages: react: 18.2.0 dev: false + /@react-types/calendar@3.4.9(react@18.2.0): + resolution: {integrity: sha512-O/PS9c21HgO9qzxOyZ7/dTccxabFZdF6tj3UED4DrBw7AN3KZ7JMzwzYbwHinOcO7nUcklGgNoAIHk45UAKR9g==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@internationalized/date': 3.5.5 + '@react-types/shared': 3.24.1(react@18.2.0) + react: 18.2.0 + dev: false + /@react-types/checkbox@3.6.0(react@18.2.0): resolution: {integrity: sha512-vgbuJzQpVCNT5AZWV0OozXCnihqrXxoZKfJFIw0xro47pT2sn3t5UC4RA9wfjDGMoK4frw1K/4HQLsQIOsPBkw==} peerDependencies: @@ -11086,6 +12158,15 @@ packages: react: 18.2.0 dev: false + /@react-types/checkbox@3.8.3(react@18.2.0): + resolution: {integrity: sha512-f4c1mnLEt0iS1NMkyZXgT3q3AgcxzDk7w6MSONOKydcnh0xG5L2oefY14DhVDLkAuQS7jThlUFwiAs+MxiO3MA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@react-types/shared': 3.24.1(react@18.2.0) + react: 18.2.0 + dev: false + /@react-types/combobox@3.10.0(react@18.2.0): resolution: {integrity: sha512-1IXSNS02TPbguyYopaW2snU6sZusbClHrEyVr4zPeexTV4kpUUBNXOzFQ+eSQRR0r2XW57Z0yRW4GJ6FGU0yCA==} peerDependencies: @@ -11095,6 +12176,15 @@ packages: react: 18.2.0 dev: false + /@react-types/combobox@3.12.1(react@18.2.0): + resolution: {integrity: sha512-bd5YwHZWtgnJx4jGbplWbYzXj7IbO5w3IY5suNR7r891rx6IktquZ8GQwyYH0pQ/x+X5LdK2xI59i6+QC2PmlA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@react-types/shared': 3.24.1(react@18.2.0) + react: 18.2.0 + dev: false + /@react-types/datepicker@3.7.1(react@18.2.0): resolution: {integrity: sha512-5juVDULOytNzkotqX8j5mYKJckeIpkgbHqVSGkPgLw0++FceIaSZ6RH56cqLup0pO45paqIt9zHh+QXBYX+syg==} peerDependencies: @@ -11107,6 +12197,28 @@ packages: react: 18.2.0 dev: false + /@react-types/datepicker@3.8.2(react@18.2.0): + resolution: {integrity: sha512-Ih4F0bNVGrEuwCD8XmmBAspuuOBsj/Svn/pDFtC2RyAZjXfWh+sI+n4XLz/sYKjvARh5TUI8GNy9smYS4vYXug==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@internationalized/date': 3.5.5 + '@react-types/calendar': 3.4.9(react@18.2.0) + '@react-types/overlays': 3.8.9(react@18.2.0) + '@react-types/shared': 3.24.1(react@18.2.0) + react: 18.2.0 + dev: false + + /@react-types/dialog@3.5.12(react@18.2.0): + resolution: {integrity: sha512-JmpQbSpXltqEyYfEwoqDolABIiojeExkqolHNdQlayIsfFuSxZxNwXZPOpz58Ri/iwv21JP7K3QF0Gb2Ohxl9w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@react-types/overlays': 3.8.9(react@18.2.0) + '@react-types/shared': 3.24.1(react@18.2.0) + react: 18.2.0 + dev: false + /@react-types/dialog@3.5.7(react@18.2.0): resolution: {integrity: sha512-geYoqAyQaTLG43AaXdMUVqZXYgkSifrD9cF7lR2kPAT0uGFv0YREi6ieU+aui8XJ83EW0xcxP+EPWd2YkN4D4w==} peerDependencies: @@ -11126,6 +12238,15 @@ packages: react: 18.2.0 dev: false + /@react-types/grid@3.2.8(react@18.2.0): + resolution: {integrity: sha512-6PJrpukwMqlv3IhJSDkJuVbhHM8Oe6hd2supWqd9adMXrlSP7QHt9a8SgFcFblCCTx8JzUaA0PvY5sTudcEtOQ==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@react-types/shared': 3.24.1(react@18.2.0) + react: 18.2.0 + dev: false + /@react-types/link@3.5.2(react@18.2.0): resolution: {integrity: sha512-/s51/WejmpLiyxOgP89s4txgxYoGaPe8pVDItVo1h4+BhU1Puyvgv/Jx8t9dPvo6LUXbraaN+SgKk/QDxaiirw==} peerDependencies: @@ -11135,6 +12256,15 @@ packages: react: 18.2.0 dev: false + /@react-types/link@3.5.7(react@18.2.0): + resolution: {integrity: sha512-2WyaVmm1qr9UrSG3Dq6iz+2ziuVp+DH8CsYZ9CA6aNNb6U18Hxju3LTPb4a5gM0eC7W0mQGNBmrgGlAdDZEJOw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@react-types/shared': 3.24.1(react@18.2.0) + react: 18.2.0 + dev: false + /@react-types/listbox@3.4.6(react@18.2.0): resolution: {integrity: sha512-XOQvrTqNh5WIPDvKiWiep8T07RAsMfjAXTjDbnjxVlKACUXkcwpts9kFaLnJ9LJRFt6DwItfP+WMkzvmx63/NQ==} peerDependencies: @@ -11144,6 +12274,25 @@ packages: react: 18.2.0 dev: false + /@react-types/listbox@3.5.1(react@18.2.0): + resolution: {integrity: sha512-n5bOgD9lgfK1qaLtag9WPnu151SwXBCNn/OgGY/Br9mWRl+nPUEYtFcPX+2VCld7uThf54kwrTmzlFnaraIlcw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@react-types/shared': 3.24.1(react@18.2.0) + react: 18.2.0 + dev: false + + /@react-types/menu@3.9.11(react@18.2.0): + resolution: {integrity: sha512-IguQVF70d7aHXgWB1Rd2a/PiIuLZ2Nt7lyayJshLcy/NLOYmgpTmTyn2WCtlA5lTfQwmQrNFf4EvnWkeljJXdA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@react-types/overlays': 3.8.9(react@18.2.0) + '@react-types/shared': 3.24.1(react@18.2.0) + react: 18.2.0 + dev: false + /@react-types/menu@3.9.6(react@18.2.0): resolution: {integrity: sha512-w/RbFInOf4nNayQDv5c2L8IMJbcFOkBhsT3xvvpTy+CHvJcQdjggwaV1sRiw7eF/PwB81k2CwigmidUzHJhKDg==} peerDependencies: @@ -11163,6 +12312,15 @@ packages: react: 18.2.0 dev: false + /@react-types/meter@3.4.3(react@18.2.0): + resolution: {integrity: sha512-Y2fX5CTAPGRKxVSeepbeyN6/K+wlF9pMRcNxTSU2qDwdoFqNCtTWMcWuCsU/Y2L/zU0jFWu4x0Vo7WkrcsgcMA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@react-types/progress': 3.5.6(react@18.2.0) + react: 18.2.0 + dev: false + /@react-types/numberfield@3.7.0(react@18.2.0): resolution: {integrity: sha512-gaGi+vqm1Y8LCWRsWYUjcGftPIzl+8W2VOfkgKMLM8y76nnwTPtmAqs+Ap1cg7sEJSfsiKMq93e9yvP3udrC2w==} peerDependencies: @@ -11172,6 +12330,15 @@ packages: react: 18.2.0 dev: false + /@react-types/numberfield@3.8.5(react@18.2.0): + resolution: {integrity: sha512-LVWggkxwd1nyVZomXBPfQA1E4I4/i4PBifjcDs2AfcV7q5RE9D+DVIDXsYucVOBxPlDOxiAq/T9ypobspWSwHw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@react-types/shared': 3.24.1(react@18.2.0) + react: 18.2.0 + dev: false + /@react-types/overlays@3.8.4(react@18.2.0): resolution: {integrity: sha512-pfgNlQnbF6RB/R2oSxyqAP3Uzz0xE/k5q4n5gUeCDNLjY5qxFHGE8xniZZ503nZYw6VBa9XMN1efDOKQyeiO0w==} peerDependencies: @@ -11181,6 +12348,15 @@ packages: react: 18.2.0 dev: false + /@react-types/overlays@3.8.9(react@18.2.0): + resolution: {integrity: sha512-9ni9upQgXPnR+K9cWmbYWvm3ll9gH8P/XsEZprqIV5zNLMF334jADK48h4jafb1X9RFnj0WbHo6BqcSObzjTig==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@react-types/shared': 3.24.1(react@18.2.0) + react: 18.2.0 + dev: false + /@react-types/progress@3.5.1(react@18.2.0): resolution: {integrity: sha512-CqsUjczUK/SfuFzDcajBBaXRTW0D3G9S/yqLDj9e8E0ii+lGDLt1PHj24t1J7E88U2rVYqmM9VL4NHTt8o3IYA==} peerDependencies: @@ -11190,6 +12366,15 @@ packages: react: 18.2.0 dev: false + /@react-types/progress@3.5.6(react@18.2.0): + resolution: {integrity: sha512-Nh43sjQ5adyN1bTHBPRaIPhXUdBqP0miYeJpeMY3V/KUl4qmouJLwDnccwFG4xLm6gBfYe22lgbbV7nAfNnuTQ==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@react-types/shared': 3.24.1(react@18.2.0) + react: 18.2.0 + dev: false + /@react-types/radio@3.7.0(react@18.2.0): resolution: {integrity: sha512-EcwGAXzSHjSqpFZha7xn3IUrhPiJLj+0yb1Ip0qPmhWz0VVw2DwrkY7q/jfaKroVvQhTo2TbfGhcsAQrt0fRqg==} peerDependencies: @@ -11199,6 +12384,15 @@ packages: react: 18.2.0 dev: false + /@react-types/radio@3.8.3(react@18.2.0): + resolution: {integrity: sha512-fUVJt4Bb6jOReFqnhHVNxWXH7t6c60uSFfoPKuXt/xI9LL1i2jhpur0ggpTfIn3qLIAmNBU6bKBCWAdr4KjeVQ==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@react-types/shared': 3.24.1(react@18.2.0) + react: 18.2.0 + dev: false + /@react-types/searchfield@3.5.2(react@18.2.0): resolution: {integrity: sha512-JAK2/Kg4Dr393FYfbRw0TlXKnJPX77sq1x/ZBxtO6p64+MuuIYKqw0i9PwDlo1PViw2QI5u8GFhKA2TgemY9uA==} peerDependencies: @@ -11209,6 +12403,16 @@ packages: react: 18.2.0 dev: false + /@react-types/searchfield@3.5.8(react@18.2.0): + resolution: {integrity: sha512-EcdqalHNIC6BJoRfmqUhAvXRd3aHkWlV1cFCz57JJKgUEFYyXPNrXd1b73TKLzTXEk+X/D6LKV15ILYpEaxu8w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@react-types/shared': 3.24.1(react@18.2.0) + '@react-types/textfield': 3.9.6(react@18.2.0) + react: 18.2.0 + dev: false + /@react-types/select@3.9.1(react@18.2.0): resolution: {integrity: sha512-EpKSxrnh8HdZvOF9dHQkjivAcdIp1K81FaxmvosH8Lygqh0iYXxAdZGtKLMyBoPI8YFhA+rotIzTcOqgCCnqWA==} peerDependencies: @@ -11218,6 +12422,15 @@ packages: react: 18.2.0 dev: false + /@react-types/select@3.9.6(react@18.2.0): + resolution: {integrity: sha512-cVSFR0eJLup/ht1Uto+y8uyLmHO89J6wNh65SIHb3jeVz9oLBAedP3YNI2qB+F9qFMUcA8PBSLXIIuT6gXzLgQ==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@react-types/shared': 3.24.1(react@18.2.0) + react: 18.2.0 + dev: false + /@react-types/shared@3.22.0(react@18.2.0): resolution: {integrity: sha512-yVOekZWbtSmmiThGEIARbBpnmUIuePFlLyctjvCbgJgGhz8JnEJOipLQ/a4anaWfzAgzSceQP8j/K+VOOePleA==} peerDependencies: @@ -11226,6 +12439,14 @@ packages: react: 18.2.0 dev: false + /@react-types/shared@3.24.1(react@18.2.0): + resolution: {integrity: sha512-AUQeGYEm/zDTN6zLzdXolDxz3Jk5dDL7f506F07U8tBwxNNI3WRdhU84G0/AaFikOZzDXhOZDr3MhQMzyE7Ydw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + react: 18.2.0 + dev: false + /@react-types/slider@3.7.0(react@18.2.0): resolution: {integrity: sha512-uyQXUVFfqc9SPUW0LZLMan2n232F/OflRafiHXz9viLFa9tVOupVa7GhASRAoHojwkjoJ1LjFlPih7g5dOZ0/Q==} peerDependencies: @@ -11235,6 +12456,15 @@ packages: react: 18.2.0 dev: false + /@react-types/slider@3.7.5(react@18.2.0): + resolution: {integrity: sha512-bRitwQRQjQoOcKEdPMljnvm474dwrmsc6pdsVQDh/qynzr+KO9IHuYc3qPW53WVE2hMQJDohlqtCAWQXWQ5Vcg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@react-types/shared': 3.24.1(react@18.2.0) + react: 18.2.0 + dev: false + /@react-types/switch@3.5.0(react@18.2.0): resolution: {integrity: sha512-/wNmUGjk69bP6t5k2QkAdrNN5Eb9Rz4dOyp0pCPmoeE+5haW6sV5NmtkvWX1NSc4DQz1xL/a5b+A0vxPCP22Jw==} peerDependencies: @@ -11244,6 +12474,25 @@ packages: react: 18.2.0 dev: false + /@react-types/switch@3.5.5(react@18.2.0): + resolution: {integrity: sha512-SZx1Bd+COhAOs/RTifbZG+uq/llwba7VAKx7XBeX4LeIz1dtguy5bigOBgFTMQi4qsIVCpybSWEEl+daj4XFPw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@react-types/shared': 3.24.1(react@18.2.0) + react: 18.2.0 + dev: false + + /@react-types/table@3.10.1(react@18.2.0): + resolution: {integrity: sha512-xsNh0Gm4GtNeSknZqkMsfGvc94fycmfhspGO+FzQKim2hB5k4yILwd+lHYQ2UKW6New9GVH/zN2Pd3v67IeZ2g==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@react-types/grid': 3.2.8(react@18.2.0) + '@react-types/shared': 3.24.1(react@18.2.0) + react: 18.2.0 + dev: false + /@react-types/table@3.9.2(react@18.2.0): resolution: {integrity: sha512-brw5JUANOzBa2rYNpN8AIl9nDZ9RwRZC6G/wTM/JhtirjC1S42oCtf8Ap5rWJBdmMG/5KOfcGNcAl/huyqb3gg==} peerDependencies: @@ -11263,6 +12512,15 @@ packages: react: 18.2.0 dev: false + /@react-types/tabs@3.3.9(react@18.2.0): + resolution: {integrity: sha512-3Q9kRVvg/qDyeJR/W1+C2z2OyvDWQrSLvOCvAezX5UKzww4rBEAA8OqBlyDwn7q3fiwrh/m64l6p+dbln+RdxQ==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@react-types/shared': 3.24.1(react@18.2.0) + react: 18.2.0 + dev: false + /@react-types/textfield@3.9.0(react@18.2.0): resolution: {integrity: sha512-D/DiwzsfkwlAg3uv8hoIfwju+zhB/hWDEdTvxQbPkntDr0kmN/QfI17NMSzbOBCInC4ABX87ViXLGxr940ykGA==} peerDependencies: @@ -11272,6 +12530,25 @@ packages: react: 18.2.0 dev: false + /@react-types/textfield@3.9.6(react@18.2.0): + resolution: {integrity: sha512-0uPqjJh4lYp1aL1HL9IlV8Cgp8eT0PcsNfdoCktfkLytvvBPmox2Pfm57W/d0xTtzZu2CjxhYNTob+JtGAOeXA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@react-types/shared': 3.24.1(react@18.2.0) + react: 18.2.0 + dev: false + + /@react-types/tooltip@3.4.11(react@18.2.0): + resolution: {integrity: sha512-WPikHQxeT5Lb09yJEaW6Ja3ecE0g1YM6ukWYS2v/iZLUPn5YlYrGytspuCYQNSh/u7suCz4zRLEHYCl7OCigjw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@react-types/overlays': 3.8.9(react@18.2.0) + '@react-types/shared': 3.24.1(react@18.2.0) + react: 18.2.0 + dev: false + /@react-types/tooltip@3.4.6(react@18.2.0): resolution: {integrity: sha512-RaZewdER7ZcsNL99RhVHs8kSLyzIBkwc0W6eFZrxST2MD9J5GzkVWRhIiqtFOd5U1aYnxdJ6woq72Ef+le6Vfw==} peerDependencies: @@ -11590,6 +12867,14 @@ packages: dev: true optional: true + /@rollup/rollup-darwin-arm64@4.21.1: + resolution: {integrity: sha512-AH/wNWSEEHvs6t4iJ3RANxW5ZCK3fUnmf0gyMxWCesY1AlUj8jY7GC+rQE4wd3gwmZ9XDOpL0kcFnCjtN7FXlA==} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + /@rollup/rollup-darwin-x64@4.13.2: resolution: {integrity: sha512-yUoEvnH0FBef/NbB1u6d3HNGyruAKnN74LrPAfDQL3O32e3k3OSfLrPgSJmgb3PJrBZWfPyt6m4ZhAFa2nZp2A==} cpu: [x64] @@ -12636,6 +13921,20 @@ packages: pretty-format: 27.5.1 dev: true + /@testing-library/jest-dom@6.4.8: + resolution: {integrity: sha512-JD0G+Zc38f5MBHA4NgxQMR5XtO5Jx9g86jqturNTt2WUfRmLDIY7iKkWHDCCTiDuFMre6nxAD5wHw9W5kI4rGw==} + engines: {node: '>=14', npm: '>=6', yarn: '>=1'} + dependencies: + '@adobe/css-tools': 4.4.0 + '@babel/runtime': 7.24.5 + aria-query: 5.3.0 + chalk: 3.0.0 + css.escape: 1.5.1 + dom-accessibility-api: 0.6.3 + lodash: 4.17.21 + redent: 3.0.0 + dev: false + /@tootallnate/quickjs-emscripten@0.23.0: resolution: {integrity: sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==} dev: false @@ -12769,7 +14068,6 @@ packages: /@types/cookie@0.6.0: resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} - dev: false /@types/cors@2.8.17: resolution: {integrity: sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==} @@ -12908,6 +14206,10 @@ packages: '@types/node': 18.19.20 dev: false + /@types/invariant@2.2.37: + resolution: {integrity: sha512-IwpIMieE55oGWiXkQPSBY1nw1nFs6bsKXTFskNY8sdS17K24vyEBRQZEwlRS7ZmXCWnJcQtbxWzly+cODWGs2A==} + dev: false + /@types/is-ci@3.0.0: resolution: {integrity: sha512-Q0Op0hdWbYd1iahB+IFNQcWXFq4O0Q5MwQP7uN0souuQ4rPg1vEYcnIOfr1gY+M+6rc8FGoRaBO1mOOvL29sEQ==} dependencies: @@ -13737,10 +15039,39 @@ packages: tslib: 2.6.2 dev: false + /@window-splitter/state@0.4.1: + resolution: {integrity: sha512-JbsCYEWFXs79Gxudtv+XTYE2tdidPg8oGoIbUyh9QtdYUNUO/QKI2ORk2WFNIkk2ZzTWMXquI92s1nj4sAhZ+w==} + engines: {node: '>=18.0.0'} + dependencies: + '@react-spring/rafz': 9.7.4 + '@types/invariant': 2.2.37 + big.js: 6.2.1 + invariant: 2.2.4 + universal-cookie: 7.2.0 + xstate: 5.17.4 + dev: false + /@xobotyi/scrollbar-width@1.9.5: resolution: {integrity: sha512-N8tkAACJx2ww8vFMneJmaAgmjAG1tnVBZJRLRcx061tmsLRZHSEZSLuGWnwPtunsSLvSqXQ2wfp7Mgqg1I+2dQ==} dev: false + /@xstate/react@4.1.1(@types/react@18.2.69)(react@18.2.0)(xstate@5.17.4): + resolution: {integrity: sha512-pFp/Y+bnczfaZ0V8B4LOhx3d6Gd71YKAPbzerGqydC2nsYN/mp7RZu3q/w6/kvI2hwR/jeDeetM7xc3JFZH2NA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + xstate: ^5.11.0 + peerDependenciesMeta: + xstate: + optional: true + dependencies: + react: 18.2.0 + use-isomorphic-layout-effect: 1.1.2(@types/react@18.2.69)(react@18.2.0) + use-sync-external-store: 1.2.2(react@18.2.0) + xstate: 5.17.4 + transitivePeerDependencies: + - '@types/react' + dev: false + /@xtuc/ieee754@1.2.0: resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==} @@ -14085,7 +15416,6 @@ packages: resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} dependencies: dequal: 2.0.3 - dev: true /array-buffer-byte-length@1.0.1: resolution: {integrity: sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==} @@ -14532,6 +15862,10 @@ packages: is-windows: 1.0.2 dev: false + /big.js@6.2.1: + resolution: {integrity: sha512-bCtHMwL9LeDIozFn+oNhhFoq+yQ3BNdnsLSASUxLciOb1vgvpHsIO1dsENiGMgbb4SkP5TrzWzRiLddn8ahVOQ==} + dev: false + /binary-extensions@2.2.0: resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} engines: {node: '>=8'} @@ -14889,6 +16223,14 @@ packages: escape-string-regexp: 1.0.5 supports-color: 5.5.0 + /chalk@3.0.0: + resolution: {integrity: sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==} + engines: {node: '>=8'} + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + dev: false + /chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -15339,6 +16681,11 @@ packages: resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} engines: {node: '>= 0.6'} + /cookie@0.6.0: + resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} + engines: {node: '>= 0.6'} + dev: false + /copy-anything@3.0.5: resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==} engines: {node: '>=12.13'} @@ -15580,6 +16927,10 @@ packages: engines: {node: '>= 6'} dev: true + /css.escape@1.5.1: + resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} + dev: false + /cssesc@3.0.0: resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} engines: {node: '>=4'} @@ -15971,7 +17322,6 @@ packages: /dequal@2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} - dev: true /destr@2.0.3: resolution: {integrity: sha512-2N3BOUU4gYMpTP24s5rF5iP7BDr7uNTCs4ozw3kf/eKfvWSIu93GEBi5m427YoyJoeOzQ5smuu4nNAPGb8idSQ==} @@ -16055,6 +17405,10 @@ packages: resolution: {integrity: sha512-8o+oVqLQZoruQPYy3uAAQtc6YbtSiRq5aPJBhJ82YTJRHvI6ofhYAkC81WmjFTnfUbqg6T3aCglIpU9p/5e7Cw==} dev: true + /dom-accessibility-api@0.6.3: + resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==} + dev: false + /dom-helpers@5.2.1: resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} dependencies: @@ -21842,6 +23196,10 @@ packages: resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==} dev: false + /performant-array-to-tree@1.11.0: + resolution: {integrity: sha512-YwCqIDvnaebXaKuKQhI5yJD6ryDc3FxvoeX/5ougXTKDUWb7s5S2BuBgIyftCa4sBe1+ZU5Kmi4RJy+pjjjrpw==} + dev: false + /periscopic@3.1.0: resolution: {integrity: sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==} dependencies: @@ -22926,6 +24284,53 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /react-aria@3.34.3(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-wSprEI5EojDFCm357MxnKAxJZN68OYIt6UH6N0KCo6MEUAVZMbhMSmGYjw/kLK4rI7KrbJDqGqUMQkwc93W9Ng==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + dependencies: + '@internationalized/string': 3.2.3 + '@react-aria/breadcrumbs': 3.5.16(react@18.2.0) + '@react-aria/button': 3.9.8(react@18.2.0) + '@react-aria/calendar': 3.5.11(react-dom@18.2.0)(react@18.2.0) + '@react-aria/checkbox': 3.14.6(react@18.2.0) + '@react-aria/combobox': 3.10.3(react-dom@18.2.0)(react@18.2.0) + '@react-aria/datepicker': 3.11.2(react-dom@18.2.0)(react@18.2.0) + '@react-aria/dialog': 3.5.17(react-dom@18.2.0)(react@18.2.0) + '@react-aria/dnd': 3.7.2(react-dom@18.2.0)(react@18.2.0) + '@react-aria/focus': 3.18.2(react@18.2.0) + '@react-aria/gridlist': 3.9.3(react-dom@18.2.0)(react@18.2.0) + '@react-aria/i18n': 3.12.2(react@18.2.0) + '@react-aria/interactions': 3.22.2(react@18.2.0) + '@react-aria/label': 3.7.11(react@18.2.0) + '@react-aria/link': 3.7.4(react@18.2.0) + '@react-aria/listbox': 3.13.3(react-dom@18.2.0)(react@18.2.0) + '@react-aria/menu': 3.15.3(react-dom@18.2.0)(react@18.2.0) + '@react-aria/meter': 3.4.16(react@18.2.0) + '@react-aria/numberfield': 3.11.6(react-dom@18.2.0)(react@18.2.0) + '@react-aria/overlays': 3.23.2(react-dom@18.2.0)(react@18.2.0) + '@react-aria/progress': 3.4.16(react@18.2.0) + '@react-aria/radio': 3.10.7(react@18.2.0) + '@react-aria/searchfield': 3.7.8(react@18.2.0) + '@react-aria/select': 3.14.9(react-dom@18.2.0)(react@18.2.0) + '@react-aria/selection': 3.19.3(react-dom@18.2.0)(react@18.2.0) + '@react-aria/separator': 3.4.2(react@18.2.0) + '@react-aria/slider': 3.7.11(react@18.2.0) + '@react-aria/ssr': 3.9.5(react@18.2.0) + '@react-aria/switch': 3.6.7(react@18.2.0) + '@react-aria/table': 3.15.3(react-dom@18.2.0)(react@18.2.0) + '@react-aria/tabs': 3.9.5(react-dom@18.2.0)(react@18.2.0) + '@react-aria/tag': 3.4.5(react-dom@18.2.0)(react@18.2.0) + '@react-aria/textfield': 3.14.8(react@18.2.0) + '@react-aria/tooltip': 3.7.7(react@18.2.0) + '@react-aria/utils': 3.25.2(react@18.2.0) + '@react-aria/visually-hidden': 3.8.15(react@18.2.0) + '@react-types/shared': 3.24.1(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /react-collapse@5.1.1(react@18.2.0): resolution: {integrity: sha512-k6cd7csF1o9LBhQ4AGBIdxB60SUEUMQDAnL2z1YvYNr9KoKr+nDkhN6FK7uGaBd/rYrYfrMpzpmJEIeHRYogBw==} peerDependencies: @@ -23302,6 +24707,28 @@ packages: tslib: 2.5.0 dev: false + /react-window-splitter@0.4.1(@types/react@18.2.69)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-7XjNxlsBqOySyPEf32GZXLIA7gIPLWqswnOwM4Aw15qiMChMm8einkz40RbLfvrD11EKHIU35vcUH1RDA+we9w==} + engines: {node: '>=18.0.0'} + peerDependencies: + react: '>=16' + react-dom: '>=16' + dependencies: + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.2.69)(react@18.2.0) + '@testing-library/jest-dom': 6.4.8 + '@window-splitter/state': 0.4.1 + '@xstate/react': 4.1.1(@types/react@18.2.69)(react@18.2.0)(xstate@5.17.4) + invariant: 2.2.4 + react: 18.2.0 + react-aria: 3.34.3(react-dom@18.2.0)(react@18.2.0) + react-dom: 18.2.0(react@18.2.0) + reforest: 0.13.0(@types/react@18.2.69)(react@18.2.0) + xstate: 5.17.4 + transitivePeerDependencies: + - '@types/react' + - immer + dev: false + /react@18.2.0: resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==} engines: {node: '>=0.10.0'} @@ -23447,6 +24874,19 @@ packages: resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==} dev: false + /reforest@0.13.0(@types/react@18.2.69)(react@18.2.0): + resolution: {integrity: sha512-f0It/s51f1UWCCCni0viULALDBhxWBPFnLmZRYtKcz4zYeNWqeNTdcnU/OpBry9tk+jyMQcH3MLK8UdzsAvA5w==} + peerDependencies: + react: '>=16.8' + dependencies: + performant-array-to-tree: 1.11.0 + react: 18.2.0 + zustand: 4.5.5(@types/react@18.2.69)(react@18.2.0) + transitivePeerDependencies: + - '@types/react' + - immer + dev: false + /regenerate-unicode-properties@10.1.0: resolution: {integrity: sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==} engines: {node: '>=4'} @@ -26070,6 +27510,13 @@ packages: unist-util-visit-parents: 5.1.1 dev: true + /universal-cookie@7.2.0: + resolution: {integrity: sha512-PvcyflJAYACJKr28HABxkGemML5vafHmiL4ICe3e+BEKXRMt0GaFLZhAwgv637kFFnnfiSJ8e6jknrKkMrU+PQ==} + dependencies: + '@types/cookie': 0.6.0 + cookie: 0.6.0 + dev: false + /universal-user-agent@6.0.1: resolution: {integrity: sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==} dev: false @@ -27281,6 +28728,10 @@ packages: engines: {node: '>=0.4.0'} dev: false + /xstate@5.17.4: + resolution: {integrity: sha512-KM2FYVOUJ04HlOO4TY3wEXqoYPR/XsDu+ewm+IWw0vilXqND0jVfvv04tEFwp8Mkk7I/oHXM8t1Ex9xJyUS4ZA==} + dev: false + /xtend@4.0.2: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'} @@ -27424,6 +28875,26 @@ packages: /zod@3.22.3: resolution: {integrity: sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug==} + /zustand@4.5.5(@types/react@18.2.69)(react@18.2.0): + resolution: {integrity: sha512-+0PALYNJNgK6hldkgDq2vLrw5f6g/jCInz52n9RTpropGgeAf/ioFUCdtsjCqu4gNhW9D01rUQBROoRjdzyn2Q==} + 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 + dependencies: + '@types/react': 18.2.69 + react: 18.2.0 + use-sync-external-store: 1.2.2(react@18.2.0) + dev: false + /zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} dev: true