Repository: tensorzero/tensorzero (language: rust)

Issue:
Add `deny_unknown_fields` to all structs that are part of params of any request

Tutorial Docker Compose fails since `/clients/python-asyncio` does not exist
When running `docker compose up` in the `examples/email-data-extraction` the command fails due to the script attempting to copy a missing directory.

```cd 04-email-data-extraction
❯ docker compose up --always-recreate-deps email-data-extraction

[+] Running 0/0
[+] Running 0/1l-data-extraction  Building                                                                                                                                                         0.1s 
[+] Building 0.2s (7/8)                                                                                                                                                            docker:desktop-linux 
 => [email-data-extraction internal] load build definition from Dockerfile                                                                                                                         0.0s
 => => transferring dockerfile: 297B                                                                                                                                                               0.0s
 => [email-data-extraction internal] load metadata for docker.io/library/python:3.10-slim-bookworm                                                                                                 0.1s
 => [email-data-extraction internal] load .dockerignore                                                                                                                                            0.0s
 => => transferring context: 48B                                                                                                                                                                   0.0s
 => [email-data-extraction 1/4] FROM docker.io/library/python:3.10-slim-bookworm@sha256:61912260e578182d00b5e163eb4cfb13b35fb8782c98d1df9ed584cec8939097                                           0.0s
 => [email-data-extraction internal] load build context                                                                                                                                            0.0s
 => => transferring context: 2B                                                                                                                                                                    0.0s
 => CACHED [email-data-extraction 2/4] WORKDIR /app                                                                                                                                                0.0s
 => ERROR [email-data-extraction 3/4] COPY clients/python-asyncio /app/tensorzero/python-asyncio                                                                                                   0.0s
------
[+] Running 0/1extraction 3/4] COPY clients/python-asyncio /app/tensorzero/python-asyncio:
 ⠹ Service email-data-extraction  Building                                                                                                                                                         0.3s 
failed to solve: failed to compute cache key: failed to calculate checksum of ref f2e14973-1aec-4e45-b5a2-0d1fb994b61a::c1e85amn16pfznjeftsb447ad: "/clients/python-asyncio": not found```


Discussion / hints:

Fix patch:
diff --git a/dashboard/app/components/model/ModelBadge.tsx b/dashboard/app/components/model/ModelBadge.tsx
new file mode 100644
index 000000000..6ab064622
--- /dev/null
+++ b/dashboard/app/components/model/ModelBadge.tsx
@@ -0,0 +1,106 @@
+import { Badge } from "~/components/ui/badge";
+import type { ProviderType } from "~/utils/config/models";
+
+interface ModelBadgeProps {
+  provider: ProviderType;
+}
+
+export function ModelBadge({ provider }: ModelBadgeProps) {
+  const providerInfo = formatProvider(provider);
+
+  return (
+    <Badge
+      className={`${providerInfo.className} hover:${providerInfo.className}`}
+    >
+      {providerInfo.name}
+    </Badge>
+  );
+}
+
+function formatProvider(provider: ProviderType): {
+  name: string;
+  className: string;
+} {
+  switch (provider) {
+    case "anthropic":
+      return {
+        name: "Anthropic",
+        className:
+          "bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300",
+      };
+    case "aws_bedrock":
+      return {
+        name: "AWS Bedrock",
+        className:
+          "bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-300",
+      };
+    case "azure":
+      return {
+        name: "Azure",
+        className:
+          "bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-300",
+      };
+    case "dummy":
+      return {
+        name: "Dummy",
+        className:
+          "bg-gray-100 text-gray-800 dark:bg-gray-900 dark:text-gray-300",
+      };
+    case "fireworks":
+      return {
+        name: "Fireworks",
+        className:
+          "bg-orange-100 text-orange-800 dark:bg-orange-900 dark:text-orange-300",
+      };
+    case "gcp_vertex_anthropic":
+      return {
+        name: "GCP Vertex AI (Anthropic)",
+        className: "bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-300",
+      };
+    case "gcp_vertex_gemini":
+      return {
+        name: "GCP Vertex AI (Gemini)",
+        className: "bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-300",
+      };
+    case "google_ai_studio_gemini":
+      return {
+        name: "Google AI Studio",
+        className: "bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-300",
+      };
+    case "hyperbolic":
+      return {
+        name: "Hyperbolic",
+        className: "bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-300",
+      };
+    case "mistral":
+      return {
+        name: "Mistral",
+        className:
+          "bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-300",
+      };
+    case "openai":
+      return {
+        name: "OpenAI",
+        className:
+          "bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-300",
+      };
+    case "together":
+      return {
+        name: "Together",
+        className:
+          "bg-indigo-100 text-indigo-800 dark:bg-indigo-900 dark:text-indigo-300",
+      };
+    case "vllm":
+      return {
+        name: "vLLM",
+        className:
+          "bg-cyan-100 text-cyan-800 dark:bg-cyan-900 dark:text-cyan-300",
+      };
+    case "xai":
+      return {
+        name: "xAI",
+        className:
+          "bg-pink-100 text-pink-800 dark:bg-pink-900 dark:text-pink-300",
+      };
+  }
+}
diff --git a/dashboard/app/components/ui/CountdownTimer.tsx b/dashboard/app/components/ui/CountdownTimer.tsx
new file mode 100644
index 000000000..f9d74f938
--- /dev/null
+++ b/dashboard/app/components/ui/CountdownTimer.tsx
@@ -0,0 +1,37 @@
+import { useEffect } from "react";
+
+import { useState } from "react";
+
+export function CountdownTimer({ targetDate }: { targetDate: Date }) {
+  const [timeLeft, setTimeLeft] = useState("");
+
+  useEffect(() => {
+    const timer = setInterval(() => {
+      const now = new Date();
+      const difference = targetDate.getTime() - now.getTime();
+
+      if (difference > 0) {
+        const hours = Math.floor((difference / (1000 * 60 * 60)) % 24);
+        const minutes = Math.floor((difference / 1000 / 60) % 60);
+        const seconds = Math.floor((difference / 1000) % 60);
+
+        setTimeLeft(
+          `${hours.toString().padStart(2, "0")}:${minutes
+            .toString()
+            .padStart(2, "0")}:${seconds.toString().padStart(2, "0")}`,
+        );
+      } else {
+        setTimeLeft("00:00:00");
+        clearInterval(timer);
+      }
+    }, 1000);
+
+    return () => clearInterval(timer);
+  }, [targetDate]);
+
+  return (
+    <span className="font-mono" title={targetDate.toLocaleString()}>
+      {timeLeft}
+    </span>
+  );
+}
diff --git a/dashboard/app/components/ui/badge.tsx b/dashboard/app/components/ui/badge.tsx
new file mode 100644
index 000000000..30604774a
--- /dev/null
+++ b/dashboard/app/components/ui/badge.tsx
@@ -0,0 +1,47 @@
+import * as React from "react";
+import { cva, type VariantProps } from "class-variance-authority";
+
+import { cn } from "~/utils/common";
+
+const badgeVariants = cva(
+  "inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
+  {
+    variants: {
+      variant: {
+        default: "border-transparent bg-primary text-primary-foreground",
+        secondary: "border-transparent bg-secondary text-secondary-foreground",
+        destructive:
+          "border-transparent bg-destructive text-destructive-foreground",
+        outline: "text-foreground",
+      },
+      shadow: {
+        true: "shadow",
+        false: "",
+      },
+      hover: {
+        true: "hover:bg-primary/80",
+        false: "",
+      },
+    },
+    defaultVariants: {
+      variant: "default",
+      shadow: false,
+      hover: false,
+    },
+  },
+);
+
+export interface BadgeProps
+  extends React.HTMLAttributes<HTMLDivElement>,
+    VariantProps<typeof badgeVariants> {}
+
+function Badge({ className, variant, shadow, hover, ...props }: BadgeProps) {
+  return (
+    <div
+      className={cn(badgeVariants({ variant, shadow, hover }), className)}
+      {...props}
+    />
+  );
+}
+
+export { Badge, badgeVariants };
diff --git a/dashboard/app/components/ui/progress.tsx b/dashboard/app/components/ui/progress.tsx
new file mode 100644
index 000000000..f38204ee9
--- /dev/null
+++ b/dashboard/app/components/ui/progress.tsx
@@ -0,0 +1,26 @@
+import * as React from "react";
+import * as ProgressPrimitive from "@radix-ui/react-progress";
+
+import { cn } from "~/utils/common";
+
+const Progress = React.forwardRef<
+  React.ElementRef<typeof ProgressPrimitive.Root>,
+  React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root>
+>(({ className, value, ...props }, ref) => (
+  <ProgressPrimitive.Root
+    ref={ref}
+    className={cn(
+      "relative h-2 w-full overflow-hidden rounded-full bg-primary/20",
+      className,
+    )}
+    {...props}
+  >
+    <ProgressPrimitive.Indicator
+      className="h-full w-full flex-1 bg-primary transition-all"
+      style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
+    />
+  </ProgressPrimitive.Root>
+));
+Progress.displayName = ProgressPrimitive.Root.displayName;
+
+export { Progress };
diff --git a/dashboard/app/routes/optimization/supervised-fine-tuning/FineTuningStatus.tsx b/dashboard/app/routes/optimization/supervised-fine-tuning/FineTuningStatus.tsx
new file mode 100644
index 000000000..c11ddab33
--- /dev/null
+++ b/dashboard/app/routes/optimization/supervised-fine-tuning/FineTuningStatus.tsx
@@ -0,0 +1,110 @@
+/**
+ * Component that displays the status and details of a Language Model Fine-Tuning job.
+ * Shows metadata like job ID, status, base model, function, metrics, and progress.
+ * Includes links to external job details and raw data visualization.
+ */
+
+import { Badge } from "~/components/ui/badge";
+import {
+  Clock,
+  ExternalLink,
+  Server,
+  ActivityIcon as Function,
+  BarChart2,
+  MessageSquare,
+} from "lucide-react";
+import type { SFTJobStatus } from "~/utils/supervised_fine_tuning/common";
+import { ModelBadge } from "~/components/model/ModelBadge";
+import { extractTimestampFromUUIDv7 } from "~/utils/common";
+import { MetadataItem } from "./MetadataItem";
+import { RawDataAccordion } from "./RawDataAccordion";
+import { ProgressIndicator } from "./ProgressIndicator";
+
+export default function LLMFineTuningStatus({
+  status,
+}: {
+  status: SFTJobStatus;
+}) {
+  if (status.status === "idle") return null;
+  const createdAt = extractTimestampFromUUIDv7(status.formData.jobId);
+  return (
+    <div className="container mx-auto space-y-6 bg-background p-6">
+      <div className="space-y-4">
+        <div className="space-y-2">
+          <h3 className="text-lg font-semibold">
+            Job{" "}
+            <code className="rounded bg-muted px-1 py-0.5 text-sm">
+              {status.formData.jobId}
+            </code>
+          </h3>
+          <div className="flex items-center gap-2">
+            <Badge
+              variant={
+                status.status === "running"
+                  ? "default"
+                  : status.status === "completed"
+                    ? "secondary"
+                    : "destructive"
+              }
+            >
+              {status.status}
+            </Badge>
+            <ModelBadge provider={status.modelProvider} />
+          </div>
+        </div>
+
+        <div className="space-y-2">
+          <MetadataItem
+            icon={Server}
+            label="Base Model"
+            value={status.formData.model.name}
+          />
+          <MetadataItem
+            icon={Function}
+            label="Function"
+            value={status.formData.function}
+          />
+          <MetadataItem
+            icon={BarChart2}
+            label="Metric"
+            value={status.formData.metric ?? "None"}
+          />
+          <MetadataItem
+            icon={MessageSquare}
+            label="Prompt"
+            value={status.formData.variant}
+          />
+          <MetadataItem
+            icon={Clock}
+            label="Created"
+            value={createdAt.toLocaleString()}
+            isRaw
+          />
+        </div>
+
+        {/* hide if not available from provider, eg fireworks */}
+        {status.status === "running" &&
+          "estimatedCompletionTime" in status &&
+          status.estimatedCompletionTime && (
+            <div className="max-w-lg space-y-2">
+              <ProgressIndicator
+                createdAt={createdAt}
+                estimatedCompletion={status.estimatedCompletionTime}
+              />
+            </div>
+          )}
+
+        <a
+          href={status.jobUrl}
+          target="_blank"
+          rel="noopener noreferrer"
+          className="inline-flex items-center text-primary hover:underline"
+        >
+          View Job Details
+          <ExternalLink className="ml-1 h-4 w-4" />
+        </a>
+        <RawDataAccordion rawData={status.rawData} />
+      </div>
+    </div>
+  );
+}
diff --git a/dashboard/app/routes/optimization/supervised-fine-tuning/FunctionSelector.tsx b/dashboard/app/routes/optimization/supervised-fine-tuning/FunctionSelector.tsx
index c203317a5..51606186e 100644
--- a/dashboard/app/routes/optimization/supervised-fine-tuning/FunctionSelector.tsx
+++ b/dashboard/app/routes/optimization/supervised-fine-tuning/FunctionSelector.tsx
@@ -15,14 +15,12 @@ type FunctionSelectorProps = {
   control: Control<SFTFormValues>;
   inferenceCount: number | null;
   config: Config;
-  onFunctionChange: (value: string) => void;
 };
 
 export function FunctionSelector({
   control,
   inferenceCount,
   config,
-  onFunctionChange,
 }: FunctionSelectorProps) {
   return (
     <FormField
@@ -35,7 +33,6 @@ export function FunctionSelector({
             <Select
               onValueChange={(value: string) => {
                 field.onChange(value);
-                onFunctionChange(value);
               }}
               value={field.value}
             >
diff --git a/dashboard/app/routes/optimization/supervised-fine-tuning/MetadataItem.tsx b/dashboard/app/routes/optimization/supervised-fine-tuning/MetadataItem.tsx
new file mode 100644
index 000000000..4546b1381
--- /dev/null
+++ b/dashboard/app/routes/optimization/supervised-fine-tuning/MetadataItem.tsx
@@ -0,0 +1,27 @@
+import { type LucideIcon } from "lucide-react";
+
+interface MetadataItemProps {
+  icon: LucideIcon;
+  label: string;
+  value: string;
+  isRaw?: boolean;
+}
+
+export function MetadataItem({
+  icon: Icon,
+  label,
+  value,
+  isRaw = false,
+}: MetadataItemProps) {
+  return (
+    <div className="flex items-center gap-2">
+      <Icon className="h-4 w-4" />
+      <span className="font-medium">{label}:</span>{" "}
+      {isRaw ? (
+        value
+      ) : (
+        <code className="rounded bg-muted px-1 py-0.5 text-sm">{value}</code>
+      )}
+    </div>
+  );
+}
diff --git a/dashboard/app/routes/optimization/supervised-fine-tuning/MetricSelector.tsx b/dashboard/app/routes/optimization/supervised-fine-tuning/MetricSelector.tsx
index f4d9359bb..45a11d42a 100644
--- a/dashboard/app/routes/optimization/supervised-fine-tuning/MetricSelector.tsx
+++ b/dashboard/app/routes/optimization/supervised-fine-tuning/MetricSelector.tsx
@@ -54,8 +54,6 @@ type MetricSelectorProps = {
   feedbackCount: number | null;
   curatedInferenceCount: number | null;
   config: Config;
-  onMetricChange: (value: string | null) => void;
-  onThresholdChange: (value: number) => void;
 };
 
 export function MetricSelector({
@@ -63,8 +61,6 @@ export function MetricSelector({
   feedbackCount,
   curatedInferenceCount,
   config,
-  onMetricChange,
-  onThresholdChange,
 }: MetricSelectorProps) {
   return (
     <FormField
@@ -79,7 +75,6 @@ export function MetricSelector({
                 onValueChange={(value: string) => {
                   const metricValue = value === "none" ? null : value;
                   field.onChange(metricValue);
-                  onMetricChange(metricValue);
                 }}
                 value={field.value ?? "none"}
               >
@@ -138,7 +133,6 @@ export function MetricSelector({
                         className="border-none bg-transparent focus:ring-0"
                         onChange={(e) => {
                           thresholdField.onChange(Number(e.target.value));
-                          onThresholdChange(Number(e.target.value));
                         }}
                       />
                     </div>
diff --git a/dashboard/app/routes/optimization/supervised-fine-tuning/ModelSelector.tsx b/dashboard/app/routes/optimization/supervised-fine-tuning/ModelSelector.tsx
index bdc0314cd..f9aadef40 100644
--- a/dashboard/app/routes/optimization/supervised-fine-tuning/ModelSelector.tsx
+++ b/dashboard/app/routes/optimization/supervised-fine-tuning/ModelSelector.tsx
@@ -2,8 +2,6 @@ import type { Control } from "react-hook-form";
 import { FormField, FormItem, FormLabel } from "~/components/ui/form";
 import type { SFTFormValues } from "./types";
 import { ModelOptionSchema, type ModelOption } from "./model_options";
-import type { ProviderConfigSchema } from "~/utils/config/models";
-import { z } from "zod";
 import { useState } from "react";
 import {
   Popover,
@@ -21,96 +19,7 @@ import {
   CommandList,
 } from "~/components/ui/command";
 import { cn } from "~/utils/common";
-
-type ProviderType = z.infer<typeof ProviderConfigSchema>["type"];
-
-function formatProvider(provider: ProviderType): {
-  name: string;
-  className: string;
-} {
-  switch (provider) {
-    case "anthropic":
-      return {
-        name: "Anthropic",
-        className:
-          "bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300",
-      };
-    case "aws_bedrock":
-      return {
-        name: "AWS Bedrock",
-        className:
-          "bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-300",
-      };
-    case "azure":
-      return {
-        name: "Azure",
-        className:
-          "bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-300",
-      };
-    case "dummy":
-      return {
-        name: "Dummy",
-        className:
-          "bg-gray-100 text-gray-800 dark:bg-gray-900 dark:text-gray-300",
-      };
-    case "fireworks":
-      return {
-        name: "Fireworks",
-        className:
-          "bg-orange-100 text-orange-800 dark:bg-orange-900 dark:text-orange-300",
-      };
-    case "gcp_vertex_anthropic":
-      return {
-        name: "GCP Vertex AI (Anthropic)",
-        className: "bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-300",
-      };
-    case "gcp_vertex_gemini":
-      return {
-        name: "GCP Vertex AI (Gemini)",
-        className: "bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-300",
-      };
-    case "google_ai_studio_gemini":
-      return {
-        name: "Google AI Studio",
-        className: "bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-300",
-      };
-    case "hyperbolic":
-      return {
-        name: "Hyperbolic",
-        className: "bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-300",
-      };
-    case "mistral":
-      return {
-        name: "Mistral",
-        className:
-          "bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-300",
-      };
-    case "openai":
-      return {
-        name: "OpenAI",
-        className:
-          "bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-300",
-      };
-    case "together":
-      return {
-        name: "Together",
-        className:
-          "bg-indigo-100 text-indigo-800 dark:bg-indigo-900 dark:text-indigo-300",
-      };
-    case "vllm":
-      return {
-        name: "vLLM",
-        className:
-          "bg-cyan-100 text-cyan-800 dark:bg-cyan-900 dark:text-cyan-300",
-      };
-    case "xai":
-      return {
-        name: "xAI",
-        className:
-          "bg-pink-100 text-pink-800 dark:bg-pink-900 dark:text-pink-300",
-      };
-  }
-}
+import { ModelBadge } from "~/components/model/ModelBadge";
 
 export function ModelSelector({
   control,
@@ -181,12 +90,8 @@ export function ModelSelector({
                     <div>
                       {field.value?.displayName ?? "Select a model..."}
                       {field.value?.provider && (
-                        <span
-                          className={`ml-2 inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium ${
-                            formatProvider(field.value.provider).className
-                          }`}
-                        >
-                          {formatProvider(field.value.provider).name}
+                        <span className="ml-2">
+                          <ModelBadge provider={field.value.provider} />
                         </span>
                       )}
                     </div>
@@ -233,13 +138,7 @@ export function ModelSelector({
                               />
                               <span>{model.displayName}</span>
                             </div>
-                            <span
-                              className={`ml-2 inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium ${
-                                formatProvider(model.provider).className
-                              }`}
-                            >
-                              {formatProvider(model.provider).name}
-                            </span>
+                            <ModelBadge provider={model.provider} />
                           </CommandItem>
                         ))}
                       </CommandGroup>
diff --git a/dashboard/app/routes/optimization/supervised-fine-tuning/ProgressIndicator.tsx b/dashboard/app/routes/optimization/supervised-fine-tuning/ProgressIndicator.tsx
new file mode 100644
index 000000000..12891ae06
--- /dev/null
+++ b/dashboard/app/routes/optimization/supervised-fine-tuning/ProgressIndicator.tsx
@@ -0,0 +1,36 @@
+import { Progress } from "~/components/ui/progress";
+import { CountdownTimer } from "~/components/ui/CountdownTimer";
+
+function getProgressPercentage(
+  createdAt: Date,
+  estimatedCompletion: Date,
+): number {
+  const now = new Date();
+  const total = estimatedCompletion.getTime() - createdAt.getTime();
+  const elapsed = now.getTime() - createdAt.getTime();
+
+  return Math.min(Math.max((elapsed / total) * 100, 0), 100);
+}
+
+interface ProgressIndicatorProps {
+  createdAt: Date;
+  estimatedCompletion: Date;
+}
+
+export function ProgressIndicator({
+  createdAt,
+  estimatedCompletion,
+}: ProgressIndicatorProps) {
+  return (
+    <div className="max-w-lg space-y-2">
+      <div className="flex items-center justify-between">
+        <span className="font-medium">Estimated Completion</span>
+        <CountdownTimer targetDate={estimatedCompletion} />
+      </div>
+      <Progress
+        value={getProgressPercentage(createdAt, estimatedCompletion)}
+        className="w-full"
+      />
+    </div>
+  );
+}
diff --git a/dashboard/app/routes/optimization/supervised-fine-tuning/RawDataAccordion.tsx b/dashboard/app/routes/optimization/supervised-fine-tuning/RawDataAccordion.tsx
new file mode 100644
index 000000000..cf5eb83db
--- /dev/null
+++ b/dashboard/app/routes/optimization/supervised-fine-tuning/RawDataAccordion.tsx
@@ -0,0 +1,101 @@
+import {
+  Accordion,
+  AccordionContent,
+  AccordionItem,
+  AccordionTrigger,
+} from "~/components/ui/accordion";
+import type { RawData } from "~/utils/supervised_fine_tuning/common";
+import { useEffect, useState } from "react";
+import { Textarea } from "~/components/ui/textarea";
+
+interface RawDataAccordionProps {
+  rawData: RawData;
+}
+
+type RawDataWithTimestamp = RawData & {
+  timestamp: Date;
+};
+
+export function RawDataAccordion({ rawData }: RawDataAccordionProps) {
+  const [history, setHistory] = useState<RawDataWithTimestamp[]>([]);
+  // Add state for accordion value
+  const [accordionValue, setAccordionValue] = useState<string | undefined>(
+    undefined,
+  );
+
+  // Update accordion value when new data comes in
+  useEffect(() => {
+    if (rawData && rawData.status === "error") {
+      setAccordionValue("raw-data");
+    } else {
+      setAccordionValue(undefined);
+    }
+  }, [rawData]);
+
+  // Add new raw data to history when it changes
+  useEffect(() => {
+    if (rawData) {
+      setHistory((prev) => [...prev, { ...rawData, timestamp: new Date() }]);
+    }
+  }, [rawData]);
+
+  // Add these utility functions
+  const getSerializedData = (entry: RawData) => {
+    return JSON.stringify(entry, null, 2);
+  };
+
+  const getTextAreaHeight = (text: string) => {
+    const lineCount = text.split("\n").length;
+    return Math.max(lineCount * 21, 100); // Adjust multiplier as needed
+  };
+
+  const getEntryClassName = (entry: RawData) => {
+    return entry.status === "error" ? "border-red-200" : "border-gray-200";
+  };
+
+  return (
+    <Accordion
+      type="single"
+      collapsible
+      className="w-full rounded-md border"
+      value={accordionValue}
+      onValueChange={setAccordionValue}
+    >
+      <AccordionItem value="raw-data" className="border-none">
+        <AccordionTrigger className="px-4">Raw Data History</AccordionTrigger>
+        <AccordionContent className="space-y-4 px-4">
+          {[...history].reverse().map((entry, index) => {
+            const serializedData = getSerializedData(entry);
+            const height = getTextAreaHeight(serializedData);
+
+            return (
+              <div
+                key={index}
+                className={`rounded-md border p-3 ${getEntryClassName(entry)}`}
+              >
+                <div className="mb-1 flex items-center justify-between">
+                  <div className="text-sm text-gray-500">
+                    {new Date(entry.timestamp).toLocaleTimeString()}
+                  </div>
+                  {entry.status === "error" && (
+                    <div className="text-sm font-medium text-red-600">
+                      Error
+                    </div>
+                  )}
+                </div>
+                <Textarea
+                  value={serializedData}
+                  style={{ height: `${height}px` }}
+                  className={`w-full resize-none rounded border-none bg-slate-100 p-2 font-mono text-sm focus:ring-0 dark:bg-slate-800 ${
+                    entry.status === "error" ? "bg-red-50 text-red-700" : ""
+                  }`}
+                  readOnly
+                />
+              </div>
+            );
+          })}
+        </AccordionContent>
+      </AccordionItem>
+    </Accordion>
+  );
+}
diff --git a/dashboard/app/routes/optimization/supervised-fine-tuning/SFTForm.tsx b/dashboard/app/routes/optimization/supervised-fine-tuning/SFTForm.tsx
new file mode 100644
index 000000000..9aa8e4d3c
--- /dev/null
+++ b/dashboard/app/routes/optimization/supervised-fine-tuning/SFTForm.tsx
@@ -0,0 +1,201 @@
+import { useForm, useWatch } from "react-hook-form";
+import { useFetcher } from "react-router";
+import { useEffect, useState } from "react";
+import { v7 as uuid } from "uuid";
+import { type SFTFormValues, SFTFormValuesResolver } from "./types";
+import { FunctionSelector } from "./FunctionSelector";
+import { MetricSelector } from "./MetricSelector";
+import { VariantSelector } from "./VariantSelector";
+import { ModelSelector } from "./ModelSelector";
+import { AdvancedParametersAccordion } from "./AdvancedParametersAccordion";
+import { Button } from "~/components/ui/button";
+import { Form } from "~/components/ui/form";
+import type { ChatCompletionConfig } from "~/utils/config/variant";
+import type { Config } from "~/utils/config";
+import type { CountsData } from "~/routes/api/curated_inferences/count.route";
+import { models } from "./model_options";
+
+export function SFTForm({
+  config,
+  submissionPhase,
+  setSubmissionPhase,
+}: {
+  config: Config;
+  submissionPhase: "idle" | "submitting" | "pending" | "complete";
+  setSubmissionPhase: (
+    phase: "idle" | "submitting" | "pending" | "complete",
+  ) => void;
+}) {
+  const form = useForm<SFTFormValues>({
+    defaultValues: {
+      function: "",
+      metric: "",
+      validationSplitPercent: 20,
+      maxSamples: 100000,
+      threshold: 0.5,
+      jobId: uuid(),
+    },
+    resolver: SFTFormValuesResolver,
+    mode: "onChange",
+  });
+
+  const {
+    handleSubmit,
+    formState: { errors },
+  } = form;
+
+  // Separate fetchers for counts and form submission
+  const countFetcher = useFetcher();
+  const formFetcher = useFetcher();
+
+  const watchedValues = useWatch({
+    control: form.control,
+    name: ["function", "metric", "threshold"],
+  });
+
+  // Use formFetcher for submission errors
+  const errorsOnSubmit = formFetcher.data?.errors;
+  useEffect(() => {
+    if (errorsOnSubmit) {
+      setSubmissionPhase("idle");
+    }
+  }, [errorsOnSubmit, setSubmissionPhase]);
+
+  const [counts, setCounts] = useState<CountsData>({
+    inferenceCount: null,
+    feedbackCount: null,
+    curatedInferenceCount: null,
+  });
+
+  // Update counts only from countFetcher
+  useEffect(() => {
+    if (countFetcher.data && !countFetcher.data.errors) {
+      setCounts(countFetcher.data as CountsData);
+    }
+  }, [countFetcher.data]);
+
+  // Handle count fetching with countFetcher
+  useEffect(() => {
+    const [functionName, metricName, threshold] = watchedValues;
+
+    if (functionName) {
+      const params = new URLSearchParams();
+      params.set("function", functionName);
+      if (metricName) params.set("metric", metricName);
+      if (threshold) params.set("threshold", String(threshold));
+
+      countFetcher.load(`/api/curated_inferences/count?${params}`);
+    }
+  }, [watchedValues]);
+
+  // Form submission using formFetcher
+  const onSubmit = async (data: SFTFormValues) => {
+    try {
+      const submitData = new FormData();
+      submitData.append("data", JSON.stringify(data));
+
+      formFetcher.submit(submitData, { method: "POST" });
+      setSubmissionPhase("submitting");
+    } catch (error) {
+      console.error("Submission error (likely a bug):", error);
+    }
+  };
+
+  const getChatCompletionVariantsForFunction = (): Record<
+    string,
+    ChatCompletionConfig
+  > => {
+    const selectedFunction = form.getValues("function");
+
+    if (!selectedFunction || !config?.functions[selectedFunction]) {
+      return {};
+    }
+
+    const functionConfig = config.functions[selectedFunction];
+    return Object.fromEntries(
+      Object.entries(functionConfig.variants || {}).filter(
+        (entry): entry is [string, ChatCompletionConfig] =>
+          entry[1].type === "chat_completion",
+      ),
+    );
+  };
+
+  // Sets the max samples limit based on the number of curatedInferences (if available) or inferences (if available)
+  // This means it will change when the function is selected or the metric is changed to something that actually curates inferences (i.e. not None)
+  useEffect(() => {
+    if (counts.curatedInferenceCount !== null) {
+      form.setValue(
+        "maxSamples",
+        Math.min(100000, counts.curatedInferenceCount),
+      );
+    } else if (counts.inferenceCount !== null) {
+      form.setValue("maxSamples", Math.min(100000, counts.inferenceCount));
+    }
+  }, [counts.curatedInferenceCount, counts.inferenceCount, form]);
+
+  function getButtonText() {
+    switch (submissionPhase) {
+      case "submitting":
+        return "Submitting...";
+      case "pending":
+        return "Pending...";
+      case "complete":
+        return "Complete";
+      default:
+        return "Start Fine-tuning Job";
+    }
+  }
+
+  return (
+    <div className="mt-8">
+      <Form {...form}>
+        <form
+          onSubmit={(e) => {
+            handleSubmit(onSubmit)(e);
+          }}
+          className="space-y-6"
+        >
+          <div className="space-y-6">
+            <FunctionSelector
+              control={form.control}
+              inferenceCount={counts.inferenceCount}
+              config={config}
+            />
+            {errors.function && (
+              <p className="text-sm text-red-500">{errors.function.message}</p>
+            )}
+
+            <MetricSelector
+              control={form.control}
+              feedbackCount={counts.feedbackCount}
+              curatedInferenceCount={counts.curatedInferenceCount}
+              config={config}
+            />
+            {errors.metric && (
+              <p className="text-sm text-red-500">{errors.metric.message}</p>
+            )}
+
+            <VariantSelector
+              control={form.control}
+              chatCompletionVariants={getChatCompletionVariantsForFunction()}
+            />
+
+            <ModelSelector control={form.control} models={models} />
+
+            <AdvancedParametersAccordion
+              control={form.control}
+              maxSamplesLimit={counts.inferenceCount ?? undefined}
+            />
+          </div>
+
+          <Button type="submit" disabled={submissionPhase !== "idle"}>
+            {getButtonText()}
+          </Button>
+          {errorsOnSubmit && (
+            <p className="text-sm text-red-500">{errorsOnSubmit.message}</p>
+          )}
+        </form>
+      </Form>
+    </div>
+  );
+}
diff --git a/dashboard/app/routes/optimization/supervised-fine-tuning/SFTResult.tsx b/dashboard/app/routes/optimization/supervised-fine-tuning/SFTResult.tsx
new file mode 100644
index 000000000..00cce4fad
--- /dev/null
+++ b/dashboard/app/routes/optimization/supervised-fine-tuning/SFTResult.tsx
@@ -0,0 +1,37 @@
+import { useState } from "react";
+import { Button } from "~/components/ui/button";
+import { Textarea } from "~/components/ui/textarea";
+
+interface SFTResultProps {
+  finalResult: string | null;
+}
+
+export function SFTResult({ finalResult }: SFTResultProps) {
+  const [copied, setCopied] = useState(false);
+
+  if (!finalResult) return null;
+
+  return (
+    <div className="mt-4 rounded-lg bg-gray-100 p-4">
+      <div className="mb-2 flex items-center justify-between font-medium">
+        <span>Configuration</span>
+        <Button
+          variant="outline"
+          size="sm"
+          onClick={() => {
+            navigator.clipboard.writeText(finalResult);
+            setCopied(true);
+            setTimeout(() => setCopied(false), 2000);
+          }}
+        >
+          {copied ? "Copied!" : "Copy to Clipboard"}
+        </Button>
+      </div>
+      <Textarea
+        value={finalResult}
+        className="h-48 w-full resize-none border-none bg-transparent focus:ring-0"
+        readOnly
+      />
+    </div>
+  );
+}
diff --git a/dashboard/app/routes/optimization/supervised-fine-tuning/route.tsx b/dashboard/app/routes/optimization/supervised-fine-tuning/route.tsx
index 2c45722cf..bcdbd74bc 100644
--- a/dashboard/app/routes/optimization/supervised-fine-tuning/route.tsx
+++ b/dashboard/app/routes/optimization/supervised-fine-tuning/route.tsx
@@ -1,36 +1,22 @@
-import { useFetcher, type MetaFunction } from "react-router";
+import { data, type MetaFunction } from "react-router";
 import { useEffect, useState } from "react";
-import {
-  type SFTFormValues,
-  SFTFormValuesSchema,
-  SFTFormValuesResolver,
-} from "./types";
-import { v7 as uuid } from "uuid";
-import type { SFTJob } from "~/utils/supervised_fine_tuning/common";
-import { models } from "./model_options";
+import { SFTFormValuesSchema } from "./types";
+import type {
+  SFTJob,
+  SFTJobStatus,
+} from "~/utils/supervised_fine_tuning/common";
 import { useRevalidator } from "react-router";
-import { useForm } from "react-hook-form";
 import { redirect } from "react-router";
 import { launch_sft_job } from "~/utils/supervised_fine_tuning/client";
-import type { ChatCompletionConfig } from "~/utils/config/variant";
 import { useConfig } from "~/context/config";
-import { FunctionSelector } from "./FunctionSelector";
-import { MetricSelector } from "./MetricSelector";
-import { VariantSelector } from "./VariantSelector";
 import {
   dump_model_config,
   get_fine_tuned_model_config,
 } from "~/utils/config/models";
-import { ModelSelector } from "./ModelSelector";
-import { AdvancedParametersAccordion } from "./AdvancedParametersAccordion";
-import { Button } from "~/components/ui/button";
-import { Textarea } from "~/components/ui/textarea";
-import { Form } from "~/components/ui/form";
 import type { Route } from "./+types/route";
-// The following import would be needed for type-safe fetching of counts
-// import type { Route as CuratedInferencesCount } from "../../api/curated_inferences/+types/count.route";
-import type { CountsData } from "../../api/curated_inferences/count.route";
-import type { Config } from "~/utils/config";
+import FineTuningStatus from "./FineTuningStatus";
+import { SFTResult } from "./SFTResult";
+import { SFTForm } from "./SFTForm";
 
 export const meta: MetaFunction = () => {
   return [
@@ -46,11 +32,16 @@ export const meta: MetaFunction = () => {
 export const jobStore: { [jobId: string]: SFTJob } = {};
 
 // If there is a job_id in the URL, grab it from the job store and pull it.
-export async function loader({ params }: Route.LoaderArgs) {
+export async function loader({
+  params,
+}: Route.LoaderArgs): Promise<SFTJobStatus> {
+  // for debugging ProgressIndicator without starting a real job
   const job_id = params.job_id;
 
   if (!job_id) {
-    return { status: "idle" };
+    return {
+      status: "idle",
+    };
   }
 
   const storedJob = jobStore[job_id];
@@ -59,34 +50,12 @@ export async function loader({ params }: Route.LoaderArgs) {
       status: 404,
     });
   }
-  if (storedJob.status() === "completed" || storedJob.status() === "error") {
-    return {
-      status: storedJob.status(),
-      result: storedJob.result(),
-      modelProvider: storedJob.provider(),
-    };
-  }
-
-  try {
-    // Poll for updates
-    const updatedJob = await storedJob.poll();
-    jobStore[job_id] = updatedJob;
 
-    const result = updatedJob.result();
-    const status = updatedJob.status();
-    const modelProvider = updatedJob.provider();
-
-    return {
-      status,
-      result,
-      modelProvider,
-    };
-  } catch (error) {
-    return {
-      status: "error",
-      error: error instanceof Error ? error.message : "Unknown error",
-    };
-  }
+  // Poll for updates
+  const updatedJob = await storedJob.poll();
+  jobStore[job_id] = updatedJob;
+  const status = updatedJob.status();
+  return status;
 }
 
 // The action actually launches the fine-tuning job.
@@ -99,8 +68,18 @@ export async function action({ request }: Route.ActionArgs) {
 
   const jsonData = JSON.parse(serializedFormData);
   const validatedData = SFTFormValuesSchema.parse(jsonData);
-
-  const job = await launch_sft_job(validatedData);
+  let job;
+  try {
+    job = await launch_sft_job(validatedData);
+  } catch (error) {
+    const errors = {
+      message:
+        error instanceof Error
+          ? error.message
+          : "Unknown error occurred while launching fine-tuning job",
+    };
+    return data({ errors }, { status: 500 });
+  }
   jobStore[validatedData.jobId] = job;
 
   return redirect(
@@ -113,12 +92,21 @@ export default function SupervisedFineTuning({
   loaderData,
 }: Route.ComponentProps) {
   const config = useConfig();
-  const { status, result, modelProvider } = loaderData;
+  if (loaderData.status === "error") {
+    return (
+      <div className="text-sm text-red-500">Error: {loaderData.error}</div>
+    );
+  }
+  const status = loaderData;
   const revalidator = useRevalidator();
 
+  const [submissionPhase, setSubmissionPhase] = useState<
+    "idle" | "submitting" | "pending" | "complete"
+  >("idle");
+
   // If running, periodically poll for updates on the job
   useEffect(() => {
-    if (status === "running") {
+    if (status.status === "running") {
       setSubmissionPhase("pending");
       const interval = setInterval(() => {
         revalidator.revalidate();
@@ -127,12 +115,12 @@ export default function SupervisedFineTuning({
     }
   }, [status, revalidator]);
 
-  const [submissionPhase, setSubmissionPhase] = useState<
-    "idle" | "submitting" | "pending" | "complete"
-  >("idle");
-  const finalResult = result
-    ? dump_model_config(get_fine_tuned_model_config(result, modelProvider))
-    : null;
+  const finalResult =
+    status.status === "completed"
+      ? dump_model_config(
+          get_fine_tuned_model_config(status.result, status.modelProvider),
+        )
+      : null;
   if (finalResult && submissionPhase !== "complete") {
     setSubmissionPhase("complete");
   }
@@ -142,236 +130,17 @@ export default function SupervisedFineTuning({
       <main>
         <h2 className="mb-4 text-2xl font-semibold">Supervised Fine-Tuning</h2>
         <div className="mb-6 h-px w-full bg-gray-200"></div>
-        {status === "idle" && (
-          <FineTuningForm
+        {status.status === "idle" && (
+          <SFTForm
             config={config}
             submissionPhase={submissionPhase}
             setSubmissionPhase={setSubmissionPhase}
           />
         )}
 
-        {status !== "idle" && (
-          <div className="mt-4 rounded-lg bg-gray-100 p-4">
-            <div className="mb-2 font-medium">
-              Loader Data (Last Updated: {new Date().toLocaleTimeString()})
-            </div>
-            <Textarea
-              value={JSON.stringify(loaderData, null, 2)}
-              className="h-48 w-full resize-none border-none bg-transparent focus:ring-0"
-              readOnly
-            />
-          </div>
-        )}
-
-        {finalResult && (
-          <div className="mt-4 rounded-lg bg-gray-100 p-4">
-            <div className="mb-2 font-medium">Configuration</div>
-            <Textarea
-              value={finalResult}
-              className="h-48 w-full resize-none border-none bg-transparent focus:ring-0"
-              readOnly
-            />
-          </div>
-        )}
+        {<FineTuningStatus status={status} />}
+        <SFTResult finalResult={finalResult} />
       </main>
     </div>
   );
 }
-
-function FineTuningForm({
-  config,
-  submissionPhase,
-  setSubmissionPhase,
-}: {
-  config: Config;
-  submissionPhase: "idle" | "submitting" | "pending" | "complete";
-  setSubmissionPhase: (
-    phase: "idle" | "submitting" | "pending" | "complete",
-  ) => void;
-}) {
-  const form = useForm<SFTFormValues>({
-    defaultValues: {
-      function: "",
-      metric: "",
-      validationSplitPercent: 20,
-      maxSamples: 100000,
-      threshold: 0.5,
-      jobId: uuid(),
-    },
-    resolver: SFTFormValuesResolver,
-    mode: "onChange",
-  });
-
-  const {
-    handleSubmit,
-    formState: { errors },
-  } = form;
-
-  const fetcher = useFetcher();
-
-  const [counts, setCounts] = useState<CountsData>({
-    inferenceCount: null,
-    feedbackCount: null,
-    curatedInferenceCount: null,
-  });
-
-  // Add this effect to watch for fetcher data changes
-  // This is needed because fetcher.load is not async, so it doesn't return a promise
-  // and we need to watch for data changes via the fetcher.data property
-  useEffect(() => {
-    if (fetcher.data) {
-      setCounts(fetcher.data as CountsData);
-    }
-  }, [fetcher.data]);
-
-  const fetchCounts = (
-    functionName?: string,
-    metricName?: string,
-    threshold?: number,
-  ) => {
-    const params = new URLSearchParams();
-    if (functionName) params.set("function", functionName);
-    if (metricName) params.set("metric", metricName);
-    if (threshold) params.set("threshold", String(threshold));
-
-    fetcher.load(`/api/curated_inferences/count?${params}`);
-  };
-
-  const handleFunctionChange = (value: string) => {
-    fetchCounts(
-      value,
-      form.getValues("metric") || undefined,
-      form.getValues("threshold") || undefined,
-    );
-  };
-
-  const handleMetricChange = (value: string | null) => {
-    fetchCounts(
-      form.getValues("function") || undefined,
-      value || undefined,
-      form.getValues("threshold") || undefined,
-    );
-  };
-
-  const handleThresholdChange = (value: number) => {
-    fetchCounts(
-      form.getValues("function") || undefined,
-      form.getValues("metric") || undefined,
-      value,
-    );
-  };
-
-  const getChatCompletionVariantsForFunction = (): Record<
-    string,
-    ChatCompletionConfig
-  > => {
-    const selectedFunction = form.getValues("function");
-
-    if (!selectedFunction || !config?.functions[selectedFunction]) {
-      return {};
-    }
-
-    const functionConfig = config.functions[selectedFunction];
-    return Object.fromEntries(
-      Object.entries(functionConfig.variants || {}).filter(
-        (entry): entry is [string, ChatCompletionConfig] =>
-          entry[1].type === "chat_completion",
-      ),
-    );
-  };
-
-  // Sets the max samples limit based on the number of curatedInferences (if available) or inferences (if available)
-  // This means it will change when the function is selected or the metric is changed to something that actually curates inferences (i.e. not None)
-  useEffect(() => {
-    if (counts.curatedInferenceCount !== null) {
-      form.setValue(
-        "maxSamples",
-        Math.min(100000, counts.curatedInferenceCount),
-      );
-    } else if (counts.inferenceCount !== null) {
-      form.setValue("maxSamples", Math.min(100000, counts.inferenceCount));
-    }
-  }, [counts.curatedInferenceCount, counts.inferenceCount, form]);
-
-  function getButtonText() {
-    switch (submissionPhase) {
-      case "submitting":
-        return "Submitting...";
-      case "pending":
-        return "Pending...";
-      case "complete":
-        return "Complete";
-      default:
-        return "Start Fine-tuning Job";
-    }
-  }
-
-  const onSubmit = async (data: SFTFormValues) => {
-    try {
-      const formData = {
-        ...data,
-      };
-
-      const submitData = new FormData();
-      submitData.append("data", JSON.stringify(formData));
-
-      fetcher.submit(submitData, { method: "POST" });
-      setSubmissionPhase("submitting");
-    } catch (error) {
-      console.error("Submission error:", error);
-    }
-  };
-
-  return (
-    <div className="mt-8">
-      <Form {...form}>
-        <form
-          onSubmit={(e) => {
-            handleSubmit(onSubmit)(e);
-          }}
-          className="space-y-6"
-        >
-          <div className="space-y-6">
-            <FunctionSelector
-              control={form.control}
-              inferenceCount={counts.inferenceCount}
-              config={config}
-              onFunctionChange={handleFunctionChange}
-            />
-            {errors.function && (
-              <p className="text-sm text-red-500">{errors.function.message}</p>
-            )}
-
-            <MetricSelector
-              control={form.control}
-              feedbackCount={counts.feedbackCount}
-              curatedInferenceCount={counts.curatedInferenceCount}
-              config={config}
-              onMetricChange={handleMetricChange}
-              onThresholdChange={handleThresholdChange}
-            />
-            {errors.metric && (
-              <p className="text-sm text-red-500">{errors.metric.message}</p>
-            )}
-
-            <VariantSelector
-              control={form.control}
-              chatCompletionVariants={getChatCompletionVariantsForFunction()}
-            />
-
-            <ModelSelector control={form.control} models={models} />
-
-            <AdvancedParametersAccordion
-              control={form.control}
-              maxSamplesLimit={counts.inferenceCount ?? undefined}
-            />
-          </div>
-
-          <Button type="submit" disabled={submissionPhase !== "idle"}>
-            {getButtonText()}
-          </Button>
-        </form>
-      </Form>
-    </div>
-  );
-}
diff --git a/dashboard/app/utils/common.ts b/dashboard/app/utils/common.ts
index a5ef19350..e33984ae4 100644
--- a/dashboard/app/utils/common.ts
+++ b/dashboard/app/utils/common.ts
@@ -4,3 +4,21 @@ import { twMerge } from "tailwind-merge";
 export function cn(...inputs: ClassValue[]) {
   return twMerge(clsx(inputs));
 }
+
+// extract Unix timestamp from a UUID v7 value without an external library
+export function extractTimestampFromUUIDv7(uuid: string): Date {
+  // split the UUID into its components
+  const parts = uuid.split("-");
+
+  // the second part of the UUID contains the high bits of the timestamp (48 bits in total)
+  const highBitsHex = parts[0] + parts[1].slice(0, 4);
+
+  // convert the high bits from hex to decimal
+  // the UUID v7 timestamp is the number of milliseconds since Unix epoch (January 1, 1970)
+  const timestampInMilliseconds = parseInt(highBitsHex, 16);
+
+  // convert the timestamp to a Date object
+  const date = new Date(timestampInMilliseconds);
+
+  return date;
+}
diff --git a/dashboard/app/utils/supervised_fine_tuning/client.ts b/dashboard/app/utils/supervised_fine_tuning/client.ts
index ffc56e7c0..e47af4f76 100644
--- a/dashboard/app/utils/supervised_fine_tuning/client.ts
+++ b/dashboard/app/utils/supervised_fine_tuning/client.ts
@@ -6,7 +6,7 @@ import type { SFTJob } from "./common";
 export function launch_sft_job(data: SFTFormValues): Promise<SFTJob> {
   switch (data.model.provider) {
     case "openai":
-      return OpenAISFTJob.fromFormData(data);
+      return OpenAISFTJob.from_form_data(data);
     case "fireworks":
       return FireworksSFTJob.from_form_data(data);
     default:
diff --git a/dashboard/app/utils/supervised_fine_tuning/common.ts b/dashboard/app/utils/supervised_fine_tuning/common.ts
index 6ee5c4809..d321fdd00 100644
--- a/dashboard/app/utils/supervised_fine_tuning/common.ts
+++ b/dashboard/app/utils/supervised_fine_tuning/common.ts
@@ -22,7 +22,43 @@ export function splitValidationData(
   };
 }
 
-export type SFTJobStatus = "running" | "completed" | "error";
+export type RawData =
+  | {
+      status: "ok";
+      info: unknown;
+    }
+  | {
+      status: "error";
+      message: string;
+    };
+
+// export type SFTJobStatus = {"running" | "completed" | "error" | "idle";
+export type SFTJobStatus =
+  | {
+      status: "running";
+      modelProvider: ProviderType;
+      formData: SFTFormValues;
+      jobUrl: string;
+      rawData: RawData;
+      estimatedCompletionTime?: Date;
+    }
+  | {
+      status: "completed";
+      modelProvider: ProviderType;
+      formData: SFTFormValues;
+      jobUrl: string;
+      rawData: RawData;
+      result: string;
+    }
+  | {
+      status: "error";
+      modelProvider: ProviderType;
+      formData: SFTFormValues;
+      jobUrl: string;
+      rawData: RawData;
+      error: string;
+    }
+  | { status: "idle" };
 
 // Abstract base class
 export abstract class SFTJob {
@@ -33,8 +69,6 @@ export abstract class SFTJob {
     throw new Error("Child class must implement fromFormData");
   }
 
-  abstract result(): string | undefined;
   abstract status(): SFTJobStatus;
   abstract poll(): Promise<SFTJob>;
-  abstract provider(): ProviderType;
 }
diff --git a/dashboard/app/utils/supervised_fine_tuning/fireworks.ts b/dashboard/app/utils/supervised_fine_tuning/fireworks.ts
index f8721586d..92be8c5ab 100644
--- a/dashboard/app/utils/supervised_fine_tuning/fireworks.ts
+++ b/dashboard/app/utils/supervised_fine_tuning/fireworks.ts
@@ -34,7 +34,6 @@ import { getConfig } from "../config/index.server";
 import { get_template_env, type ChatCompletionConfig } from "../config/variant";
 import { z } from "zod";
 import { SFTJob, type SFTJobStatus } from "./common";
-import type { ProviderType } from "../config/models";
 export const FIREWORKS_API_URL = "https://api.fireworks.ai";
 export const FIREWORKS_API_KEY = process.env.FIREWORKS_API_KEY || logError();
 export const FIREWORKS_ACCOUNT_ID =
@@ -52,14 +51,28 @@ interface FireworksSFTJobParams {
   jobId: string;
   modelId?: string;
   modelPath?: string;
+  jobInfo: JobInfo;
+  formData: SFTFormValues;
 }
 
+type JobInfo =
+  | {
+      status: "ok";
+      info: string | Record<string, unknown>;
+    }
+  | {
+      status: "error";
+      message: string;
+    };
+
 export class FireworksSFTJob extends SFTJob {
   public jobPath: string;
   public jobStatus: string;
   public jobId: string;
   public modelId?: string;
   public modelPath?: string;
+  public jobInfo: JobInfo;
+  public formData: SFTFormValues;
 
   constructor(params: FireworksSFTJobParams) {
     super();
@@ -68,6 +81,8 @@ export class FireworksSFTJob extends SFTJob {
     this.jobId = params.jobId;
     this.modelId = params.modelId;
     this.modelPath = params.modelPath;
+    this.jobInfo = params.jobInfo;
+    this.formData = params.formData;
   }
 
   static async from_form_data(data: SFTFormValues): Promise<FireworksSFTJob> {
@@ -92,84 +107,169 @@ export class FireworksSFTJob extends SFTJob {
       throw new Error("No curated inferences found");
     }
     const templateEnv = await get_template_env(currentVariant);
-    const jobPath = await start_sft_fireworks(
-      data.model.name,
-      curatedInferences,
-      data.validationSplitPercent,
-      templateEnv,
-    );
+    let jobInfo;
+    try {
+      jobInfo = await start_sft_fireworks(
+        data.model.name,
+        curatedInferences,
+        data.validationSplitPercent,
+        templateEnv,
+      );
+    } catch (error) {
+      throw new Error(
+        `Failed to start Fireworks SFT job: ${
+          error instanceof Error ? error.message : String(error)
+        }`,
+      );
+    }
+    const jobPath = jobInfo.name as string;
     return new FireworksSFTJob({
       jobPath: jobPath,
       status: "RUNNING",
       jobId: data.jobId,
       modelId: undefined,
       modelPath: undefined,
+      jobInfo: {
+        status: "ok",
+        info: jobInfo,
+      },
+      formData: data,
     });
   }
 
-  provider(): ProviderType {
-    return "fireworks";
+  private get jobUrl(): string {
+    const jobId = this.jobPath.split("/").pop();
+    if (!jobId) {
+      throw new Error("Failed to parse job ID from path");
+    }
+    return `https://fireworks.ai/dashboard/fine-tuning/${jobId}`;
   }
 
   status(): SFTJobStatus {
-    if (this.jobStatus === "FAILED") return "error";
-    return this.jobStatus === "DEPLOYED" ? "completed" : "running";
-  }
-
-  result(): string | undefined {
-    return this.modelPath;
+    if (this.jobStatus === "FAILED") {
+      const error =
+        this.jobInfo.status === "error"
+          ? this.jobInfo.message
+          : "Unknown error";
+      return {
+        status: "error",
+        modelProvider: "fireworks",
+        formData: this.formData,
+        jobUrl: this.jobUrl,
+        rawData: this.jobInfo,
+        error: error,
+      };
+    }
+    if (this.jobStatus === "DEPLOYED") {
+      if (!this.modelPath) {
+        throw new Error("Model path is undefined for deployed job");
+      }
+      return {
+        status: "completed",
+        modelProvider: "fireworks",
+        formData: this.formData,
+        jobUrl: this.jobUrl,
+        result: this.modelPath,
+        rawData: this.jobInfo,
+      };
+    }
+    return {
+      status: "running",
+      modelProvider: "fireworks",
+      formData: this.formData,
+      jobUrl: this.jobUrl,
+      rawData: this.jobInfo,
+    };
   }
 
   async poll(): Promise<FireworksSFTJob> {
-    if (!this.modelId) {
-      // If we don't have a model ID, training is still running so we need to poll for it
-      const status = await get_fine_tuning_job_status(this.jobPath);
-      if (status === "COMPLETED") {
-        const modelId = await get_model_id(this.jobPath);
-        if (!modelId) {
-          throw new Error("Model ID not found after job completed");
+    try {
+      if (!this.modelId) {
+        // If we don't have a model ID, training is still running so we need to poll for it
+        const jobInfo = await get_fine_tuning_job_details(this.jobPath);
+        const status = jobInfo.state;
+        if (status === "COMPLETED") {
+          const modelId = jobInfo.modelId;
+          if (!modelId) {
+            throw new Error("Model ID not found after job completed");
+          }
+          // Begin deployment process
+          await deploy_model_request(FIREWORKS_ACCOUNT_ID, modelId);
+          return new FireworksSFTJob({
+            jobPath: this.jobPath,
+            status: "DEPLOYING",
+            jobId: this.jobId,
+            modelId: modelId,
+            modelPath: undefined,
+            jobInfo: {
+              status: "ok",
+              info: jobInfo,
+            },
+            formData: this.formData,
+          });
+        } else {
+          return new FireworksSFTJob({
+            jobPath: this.jobPath,
+            status: "TRAINING",
+            jobId: this.jobId,
+            modelId: undefined,
+            modelPath: undefined,
+            jobInfo: {
+              status: "ok",
+              info: jobInfo,
+            },
+            formData: this.formData,
+          });
         }
-        await deploy_model(FIREWORKS_ACCOUNT_ID, modelId);
-        return new FireworksSFTJob({
-          jobPath: this.jobPath,
-          status: "DEPLOYING",
-          jobId: this.jobId,
-          modelId: modelId,
-          modelPath: undefined,
-        });
       } else {
-        return new FireworksSFTJob({
-          jobPath: this.jobPath,
-          status: "TRAINING",
-          jobId: this.jobId,
-          modelId: undefined,
-          modelPath: undefined,
-        });
-      }
-    } else {
-      // If we do have a model ID, we need to poll for the deployment
-      const status = await poll_model_deployment(
-        FIREWORKS_ACCOUNT_ID,
-        this.modelId,
-      );
-      if (status === "DEPLOYED") {
-        const modelPath = `accounts/${FIREWORKS_ACCOUNT_ID}/models/${this.modelId}`;
-        return new FireworksSFTJob({
-          jobPath: this.jobPath,
-          status: "DEPLOYED",
-          jobId: this.jobId,
-          modelId: this.modelId,
-          modelPath: modelPath,
-        });
-      } else {
-        return new FireworksSFTJob({
-          jobPath: this.jobPath,
-          status: "DEPLOYING",
-          jobId: this.jobId,
-          modelId: this.modelId,
-          modelPath: undefined,
-        });
+        // If we do have a model ID, we need to poll for the deployment
+        const deployModelResponse = await deploy_model_request(
+          FIREWORKS_ACCOUNT_ID,
+          this.modelId,
+        );
+        const status = get_deployment_status(deployModelResponse);
+        if (status === "DEPLOYED") {
+          const modelPath = `accounts/${FIREWORKS_ACCOUNT_ID}/models/${this.modelId}`;
+          return new FireworksSFTJob({
+            jobPath: this.jobPath,
+            status: "DEPLOYED",
+            jobId: this.jobId,
+            modelId: this.modelId,
+            modelPath: modelPath,
+            jobInfo: {
+              status: "ok",
+              info: deployModelResponse,
+            },
+            formData: this.formData,
+          });
+        } else {
+          return new FireworksSFTJob({
+            jobPath: this.jobPath,
+            status: "DEPLOYING",
+            jobId: this.jobId,
+            modelId: this.modelId,
+            modelPath: undefined,
+            jobInfo: {
+              status: "ok",
+              info: deployModelResponse,
+            },
+            formData: this.formData,
+          });
+        }
       }
+    } catch (error) {
+      return new FireworksSFTJob({
+        jobPath: this.jobPath,
+        status: "error",
+        jobId: this.jobId,
+        modelId: this.modelId,
+        modelPath: undefined,
+        jobInfo: {
+          status: "error",
+          message: error instanceof Error ? error.message : String(error),
+        },
+        formData: this.formData,
+      });
     }
   }
 }
@@ -184,12 +284,33 @@ const FineTuningJobStatusSchema = z.enum([
   "DELETING",
 ]);
 
-type FineTuningJobStatus = z.infer<typeof FineTuningJobStatusSchema>;
-
 const FineTuningJobResponseSchema = z.object({
   state: FineTuningJobStatusSchema,
   modelId: z.string().optional(),
-  // Add more fields as needed
+  baseModel: z.string().optional(),
+  batchSize: z.number().optional(),
+  createTime: z.string().optional(),
+  createdBy: z.string().optional(),
+  dataset: z.string().optional(),
+  evaluationSplit: z.number().optional(),
+  fineTuningJobId: z.string().optional(),
+  fineTuningJobName: z.string().optional(),
+  fineTuningJobPath: z.string().optional(),
+  evaluation: z.boolean().optional(),
+  evaluationDataset: z.string().optional(),
+  learningRate: z.number().optional(),
+  loraRank: z.number().optional(),
+  loraTargetModules: z.array(z.string()).optional(),
+  maskToken: z.string().optional(),
+  microBatchSize: z.number().optional(),
+  name: z.string().optional(),
+  padToken: z.string().optional(),
+  status: z
+    .object({
+      code: z.string().optional(),
+      message: z.string().optional(),
+    })
+    .optional(),
 });
 
 type FineTuningJobResponse = z.infer<typeof FineTuningJobResponseSchema>;
@@ -229,24 +350,13 @@ async function get_fine_tuning_job_details(
   }
 }
 
-async function get_fine_tuning_job_status(
-  job_path: string,
-): Promise<FineTuningJobStatus> {
-  const response = await get_fine_tuning_job_details(job_path);
-  return response.state;
-}
-
-// This is the model ID that we can use to deploy the model
-// Note: this should only be called after the job is completed
-async function get_model_id(job_path: string): Promise<string | undefined> {
-  const response = await get_fine_tuning_job_details(job_path);
-  return response.modelId;
-}
-
 // Docs: https://docs.fireworks.ai/api-reference/create-deployment
 // Once a model has been fine-tuned, we should deploy it
 // This is a separate step from the fine-tuning job
 // NOTE: If unused, the model will be un-deployed after 7 days
+// This function is called both to deploy the model and to poll for the deployment status
+// NOTE: If the model has already been requested to be deployed,
+// the API actually returns 400 along with the deployment status in the message
 async function deploy_model_request(accountId: string, modelId: string) {
   const url = new URL(
     `v1/accounts/${accountId}/deployedModels`,
@@ -271,29 +381,28 @@ async function deploy_model_request(accountId: string, modelId: string) {
     body: JSON.stringify(body),
   };
 
-  const response = await fetch(url, options).then((r) => r.json());
-
-  return response;
-}
-
-async function deploy_model(
-  accountId: string,
-  modelId: string,
-): Promise<string> {
-  const response = await deploy_model_request(accountId, modelId);
-  return response.name;
+  try {
+    const response = await fetch(url, options);
+    const data = await response.json();
+    if (!data) {
+      throw new Error("Empty response received from deploy model request");
+    }
+    return data;
+  } catch (error) {
+    throw new Error(
+      `Error deploying model: ${
+        error instanceof Error ? error.message : "Unknown error"
+      }`,
+    );
+  }
 }
 
-// Returns the status of the deployment
-// TODO: this should be a better API call honestly
-// We just can't find the right endpoint
-async function poll_model_deployment(
-  accountId: string,
-  modelId: string,
-): Promise<string> {
-  const response = await deploy_model_request(accountId, modelId);
-  const message = response.message;
-  if (!message) {
+// Extracts the deployment status from the deploy model response
+function get_deployment_status(
+  deployModelResponse: Record<string, unknown>,
+): string {
+  const message = deployModelResponse.message;
+  if (!message || typeof message !== "string") {
     throw new Error("Failed to get deployment status message");
   }
   const status = message.split(":").pop()?.trim();
@@ -308,7 +417,7 @@ export async function start_sft_fireworks(
   inferences: ParsedInferenceRow[],
   validationSplitPercent: number,
   templateEnv: JsExposedEnv,
-): Promise<string> {
+): Promise<Record<string, unknown>> {
   const fireworksExamples = inferences.map((inference) =>
     tensorzero_inference_to_fireworks_messages(inference, templateEnv),
   );
@@ -324,14 +433,14 @@ export async function start_sft_fireworks(
     await new Promise((resolve) => setTimeout(resolve, 1000));
   }
 
-  const job_path = await create_fine_tuning_job(
+  const jobInfo = await create_fine_tuning_job(
     FIREWORKS_ACCOUNT_ID,
     datasetId,
     modelName,
     validationSplitPercent,
   );
 
-  return job_path;
+  return jobInfo;
 }
 
 type FireworksMessage = {
@@ -432,8 +541,21 @@ async function create_dataset_record(accountId: string, exampleCount: number) {
       },
     }),
   };
-  await fetch(url, options).then((r) => r.json());
-  // TODO(Viraj: check it more robustly)
+  try {
+    const response = await fetch(url, options);
+    if (!response.ok) {
+      throw new Error(
+        `Failed to create dataset record: ${response.status} ${response.statusText}`,
+      );
+    }
+    await response.json();
+  } catch (error) {
+    throw new Error(
+      `Error creating dataset record: ${
+        error instanceof Error ? error.message : "Unknown error"
+      }`,
+    );
+  }
 
   return datasetId;
 }
@@ -471,9 +593,26 @@ async function upload_dataset(
     body: form,
   };
 
-  const response = await fetch(url, options).then((r) => r.json());
-
-  return response;
+  let response;
+  try {
+    response = await fetch(url, options);
+    if (!response.ok) {
+      throw new Error(
+        `Failed to upload dataset: ${response.status} ${response.statusText}`,
+      );
+    }
+    const data = await response.json();
+    if (!data) {
+      throw new Error("Empty response received from upload dataset request");
+    }
+    return data;
+  } catch (error) {
+    throw new Error(
+      `Error uploading dataset: ${
+        error instanceof Error ? error.message : "Unknown error"
+      }`,
+    );
+  }
 }
 
 // Returns true if the dataset is ready for fine-tuning
@@ -520,7 +659,7 @@ async function create_fine_tuning_job(
   datasetId: string,
   baseModel: string,
   valSplit: number,
-): Promise<string> {
+): Promise<Record<string, unknown>> {
   const url = new URL(
     `v1/accounts/${accountId}/fineTuningJobs`,
     FIREWORKS_API_URL,
@@ -553,7 +692,7 @@ async function create_fine_tuning_job(
     if (!data.name) {
       throw new Error("Fine tuning job response missing name field");
     }
-    return data.name;
+    return data;
   } catch (error) {
     throw new Error(
       `Error creating fine tuning job: ${
diff --git a/dashboard/app/utils/supervised_fine_tuning/openai.ts b/dashboard/app/utils/supervised_fine_tuning/openai.ts
index 47ecd3564..d20a2853d 100644
--- a/dashboard/app/utils/supervised_fine_tuning/openai.ts
+++ b/dashboard/app/utils/supervised_fine_tuning/openai.ts
@@ -18,31 +18,46 @@ import type { JsExposedEnv } from "../minijinja/pkg/minijinja_bindings";
 import { splitValidationData, type SFTJobStatus } from "./common";
 import { render_message } from "./rendering";
 import { SFTJob } from "./common";
-import type { ProviderType } from "../config/models";
 
 export const client = new OpenAI({
   apiKey: process.env.OPENAI_API_KEY,
 });
 
+type JobInfo =
+  | {
+      status: "ok";
+      info: OpenAI.FineTuning.Jobs.FineTuningJob;
+    }
+  | {
+      status: "error";
+      message: string;
+      info: OpenAI.FineTuning.Jobs.FineTuningJob;
+    };
+
 interface OpenAISFTJobParams {
   jobId: string;
   status: string;
   fineTunedModel?: string;
+  job: JobInfo;
+  formData: SFTFormValues;
 }
 
 export class OpenAISFTJob extends SFTJob {
   public jobId: string;
   public jobStatus: string;
   public fineTunedModel?: string;
-
+  public job: JobInfo;
+  public formData: SFTFormValues;
   constructor(params: OpenAISFTJobParams) {
     super();
     this.jobId = params.jobId;
     this.jobStatus = params.status;
     this.fineTunedModel = params.fineTunedModel;
+    this.job = params.job;
+    this.formData = params.formData;
   }
 
-  static async fromFormData(data: SFTFormValues): Promise<OpenAISFTJob> {
+  static async from_form_data(data: SFTFormValues): Promise<OpenAISFTJob> {
     const config = await getConfig();
     const currentVariant = config.functions[data.function].variants[
       data.variant
@@ -64,38 +79,97 @@ export class OpenAISFTJob extends SFTJob {
       throw new Error("No curated inferences found");
     }
     const templateEnv = await get_template_env(currentVariant);
-    const job = await start_sft_openai(
-      data.model.name,
-      curatedInferences,
-      data.validationSplitPercent,
-      templateEnv,
-    );
+    let job;
+    try {
+      job = await start_sft_openai(
+        data.model.name,
+        curatedInferences,
+        data.validationSplitPercent,
+        templateEnv,
+        data,
+      );
+    } catch (error) {
+      throw new Error(
+        `Failed to start OpenAI SFT job: ${
+          error instanceof Error ? error.message : String(error)
+        }`,
+      );
+    }
     return job;
   }
-
-  result(): string | undefined {
-    return this.fineTunedModel;
-  }
-
-  provider(): ProviderType {
-    return "openai";
+  private get jobUrl(): string {
+    return `https://platform.openai.com/finetune/${this.jobId}`;
   }
 
   status(): SFTJobStatus {
-    if (this.jobStatus === "failed") return "error";
-    return this.jobStatus === "succeeded" ? "completed" : "running";
+    if (this.jobStatus === "failed") {
+      const error =
+        this.job.status === "error" ? this.job.message : "Unknown error";
+      return {
+        status: "error",
+        modelProvider: "openai",
+        formData: this.formData,
+        jobUrl: this.jobUrl,
+        rawData: this.job,
+        error: error,
+      };
+    }
+    if (this.jobStatus === "succeeded") {
+      if (!this.fineTunedModel) {
+        throw new Error("Fine-tuned model is undefined");
+      }
+      return {
+        status: "completed",
+        modelProvider: "openai",
+        formData: this.formData,
+        jobUrl: this.jobUrl,
+        rawData: this.job,
+        result: this.fineTunedModel,
+      };
+    }
+    const estimatedCompletionTime =
+      this.job.status === "ok" ? this.job.info.estimated_finish : undefined;
+    return {
+      status: "running",
+      modelProvider: "openai",
+      formData: this.formData,
+      rawData: this.job,
+      jobUrl: this.jobUrl,
+      estimatedCompletionTime: estimatedCompletionTime
+        ? new Date(estimatedCompletionTime * 1000)
+        : undefined,
+    };
   }
 
   async poll(): Promise<OpenAISFTJob> {
     if (!this.jobId) {
       throw new Error("Job ID is required to poll OpenAI SFT");
     }
-    const job = await client.fineTuning.jobs.retrieve(this.jobId);
-    return new OpenAISFTJob({
-      jobId: job.id,
-      status: job.status,
-      fineTunedModel: job.fine_tuned_model ?? undefined,
-    });
+    try {
+      const jobInfo = await client.fineTuning.jobs.retrieve(this.jobId);
+      return new OpenAISFTJob({
+        jobId: jobInfo.id,
+        status: jobInfo.status,
+        fineTunedModel: jobInfo.fine_tuned_model ?? undefined,
+        job: {
+          status: "ok",
+          info: jobInfo,
+        },
+        formData: this.formData,
+      });
+    } catch (error) {
+      return new OpenAISFTJob({
+        jobId: this.jobId,
+        status: "running",
+        fineTunedModel: undefined,
+        job: {
+          status: "error",
+          info: this.job.info,
+          message: error instanceof Error ? error.message : String(error),
+        },
+        formData: this.formData,
+      });
+    }
   }
 }
 
@@ -104,6 +178,7 @@ export async function start_sft_openai(
   inferences: ParsedInferenceRow[],
   validationSplitPercent: number,
   templateEnv: JsExposedEnv,
+  formData: SFTFormValues,
 ) {
   const { trainInferences, valInferences } = splitValidationData(
     inferences,
@@ -119,15 +194,21 @@ export async function start_sft_openai(
     upload_examples_to_openai(trainMessages),
     upload_examples_to_openai(valMessages),
   ]);
-  const job_id = await create_openai_fine_tuning_job(
+  const job = await create_openai_fine_tuning_job(
     modelName,
     file_id,
     val_file_id ?? undefined,
   );
+  const jobId = job.id;
   return new OpenAISFTJob({
-    jobId: job_id,
+    jobId: jobId,
     status: "created",
     fineTunedModel: undefined,
+    job: {
+      status: "ok",
+      info: job,
+    },
+    formData,
   });
 }
 
@@ -276,7 +357,7 @@ async function create_openai_fine_tuning_job(
 
   try {
     const job = await client.fineTuning.jobs.create(params);
-    return job.id;
+    return job;
   } catch (error) {
     console.error("Error creating fine-tuning job:", error);
     throw error;
diff --git a/dashboard/package-lock.json b/dashboard/package-lock.json
index 93c4b1df9..ae1668d55 100644
--- a/dashboard/package-lock.json
+++ b/dashboard/package-lock.json
@@ -68,6 +68,7 @@
       "version": "5.2.0",
       "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
       "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
+      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">=10"
@@ -584,6 +585,7 @@
       "cpu": [
         "ppc64"
       ],
+      "dev": true,
       "license": "MIT",
       "optional": true,
       "os": [
@@ -600,6 +602,7 @@
       "cpu": [
         "arm"
       ],
+      "dev": true,
       "license": "MIT",
       "optional": true,
       "os": [
@@ -616,6 +619,7 @@
       "cpu": [
         "arm64"
       ],
+      "dev": true,
       "license": "MIT",
       "optional": true,
       "os": [
@@ -632,6 +636,7 @@
       "cpu": [
         "x64"
       ],
+      "dev": true,
       "license": "MIT",
       "optional": true,
       "os": [
@@ -648,6 +653,7 @@
       "cpu": [
         "arm64"
       ],
+      "dev": true,
       "license": "MIT",
       "optional": true,
       "os": [
@@ -664,6 +670,7 @@
       "cpu": [
         "x64"
       ],
+      "dev": true,
       "license": "MIT",
       "optional": true,
       "os": [
@@ -680,6 +687,7 @@
       "cpu": [
         "arm64"
       ],
+      "dev": true,
       "license": "MIT",
       "optional": true,
       "os": [
@@ -696,6 +704,7 @@
       "cpu": [
         "x64"
       ],
+      "dev": true,
       "license": "MIT",
       "optional": true,
       "os": [
@@ -712,6 +721,7 @@
       "cpu": [
         "arm"
       ],
+      "dev": true,
       "license": "MIT",
       "optional": true,
       "os": [
@@ -728,6 +738,7 @@
       "cpu": [
         "arm64"
       ],
+      "dev": true,
       "license": "MIT",
       "optional": true,
       "os": [
@@ -744,6 +755,7 @@
       "cpu": [
         "ia32"
       ],
+      "dev": true,
       "license": "MIT",
       "optional": true,
       "os": [
@@ -760,6 +772,7 @@
       "cpu": [
         "loong64"
       ],
+      "dev": true,
       "license": "MIT",
       "optional": true,
       "os": [
@@ -776,6 +789,7 @@
       "cpu": [
         "mips64el"
       ],
+      "dev": true,
       "license": "MIT",
       "optional": true,
       "os": [
@@ -792,6 +806,7 @@
       "cpu": [
         "ppc64"
       ],
+      "dev": true,
       "license": "MIT",
       "optional": true,
       "os": [
@@ -808,6 +823,7 @@
       "cpu": [
         "riscv64"
       ],
+      "dev": true,
       "license": "MIT",
       "optional": true,
       "os": [
@@ -824,6 +840,7 @@
       "cpu": [
         "s390x"
       ],
+      "dev": true,
       "license": "MIT",
       "optional": true,
       "os": [
@@ -840,6 +857,7 @@
       "cpu": [
         "x64"
       ],
+      "dev": true,
       "license": "MIT",
       "optional": true,
       "os": [
@@ -856,6 +874,7 @@
       "cpu": [
         "x64"
       ],
+      "dev": true,
       "license": "MIT",
       "optional": true,
       "os": [
@@ -872,6 +891,7 @@
       "cpu": [
         "x64"
       ],
+      "dev": true,
       "license": "MIT",
       "optional": true,
       "os": [
@@ -888,6 +908,7 @@
       "cpu": [
         "x64"
       ],
+      "dev": true,
       "license": "MIT",
       "optional": true,
       "os": [
@@ -904,6 +925,7 @@
       "cpu": [
         "arm64"
       ],
+      "dev": true,
       "license": "MIT",
       "optional": true,
       "os": [
@@ -920,6 +942,7 @@
       "cpu": [
         "ia32"
       ],
+      "dev": true,
       "license": "MIT",
       "optional": true,
       "os": [
@@ -936,6 +959,7 @@
       "cpu": [
         "x64"
       ],
+      "dev": true,
       "license": "MIT",
       "optional": true,
       "os": [
@@ -988,6 +1012,7 @@
       "version": "0.19.1",
       "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.1.tgz",
       "integrity": "sha512-fo6Mtm5mWyKjA/Chy1BYTdn5mGJoDNjC7C64ug20ADsRDGrA85bN3uK3MaKbeRkRuuIEAR5N33Jr1pbm411/PA==",
+      "dev": true,
       "license": "Apache-2.0",
       "dependencies": {
         "@eslint/object-schema": "^2.1.5",
@@ -1002,6 +1027,7 @@
       "version": "0.9.1",
       "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.9.1.tgz",
       "integrity": "sha512-GuUdqkyyzQI5RMIWkHhvTWLCyLo1jNK3vzkSyaExH5kHPDHcuL2VOpHjmMY+y3+NC69qAKToBqldTBgYeLSr9Q==",
+      "dev": true,
       "license": "Apache-2.0",
       "dependencies": {
         "@types/json-schema": "^7.0.15"
@@ -1014,6 +1040,7 @@
       "version": "3.2.0",
       "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.2.0.tgz",
       "integrity": "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==",
+      "dev": true,
       "license": "MIT",
       "dependencies": {
         "ajv": "^6.12.4",
@@ -1037,6 +1064,7 @@
       "version": "6.12.6",
       "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
       "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+      "dev": true,
       "license": "MIT",
       "dependencies": {
         "fast-deep-equal": "^3.1.1",
@@ -1053,6 +1081,7 @@
       "version": "14.0.0",
       "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
       "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
+      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">=18"
@@ -1065,6 +1094,7 @@
       "version": "0.4.1",
       "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
       "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+      "dev": true,
       "license": "MIT"
     },
     "node_modules/@eslint/js": {
@@ -1080,6 +1110,7 @@
       "version": "2.1.5",
       "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.5.tgz",
       "integrity": "sha512-o0bhxnL89h5Bae5T318nFoFzGy+YE5i/gGkoPAgkmTVdRKTiv3p8JHevPiPaMwoloKfEiiaHlawCqaZMqRm+XQ==",
+      "dev": true,
       "license": "Apache-2.0",
       "engines": {
         "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1089,6 +1120,7 @@
       "version": "0.2.4",
       "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.4.tgz",
       "integrity": "sha512-zSkKow6H5Kdm0ZUQUB2kV5JIXqoG0+uH5YADhaEHswm664N9Db8dXSi0nMJpacpMf+MyyglF1vnZohpEg5yUtg==",
+      "dev": true,
       "license": "Apache-2.0",
       "dependencies": {
         "levn": "^0.4.1"
@@ -1148,6 +1180,7 @@
       "version": "0.19.1",
       "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
       "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==",
+      "dev": true,
       "license": "Apache-2.0",
       "engines": {
         "node": ">=18.18.0"
@@ -1157,6 +1190,7 @@
       "version": "0.16.6",
       "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz",
       "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==",
+      "dev": true,
       "license": "Apache-2.0",
       "dependencies": {
         "@humanfs/core": "^0.19.1",
@@ -1170,6 +1204,7 @@
       "version": "0.3.1",
       "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz",
       "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==",
+      "dev": true,
       "license": "Apache-2.0",
       "engines": {
         "node": ">=18.18"
@@ -1183,6 +1218,7 @@
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
       "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+      "dev": true,
       "license": "Apache-2.0",
       "engines": {
         "node": ">=12.22"
@@ -1196,6 +1232,7 @@
       "version": "0.4.1",
       "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz",
       "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==",
+      "dev": true,
       "license": "Apache-2.0",
       "engines": {
         "node": ">=18.18"
@@ -1209,6 +1246,7 @@
       "version": "8.0.2",
       "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
       "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
+      "dev": true,
       "license": "ISC",
       "dependencies": {
         "string-width": "^5.1.2",
@@ -1226,6 +1264,7 @@
       "version": "0.3.8",
       "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
       "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==",
+      "dev": true,
       "license": "MIT",
       "dependencies": {
         "@jridgewell/set-array": "^1.2.1",
@@ -1240,6 +1279,7 @@
       "version": "3.1.2",
       "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
       "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">=6.0.0"
@@ -1249,6 +1289,7 @@
       "version": "1.2.1",
       "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
       "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
+      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">=6.0.0"
@@ -1258,12 +1299,14 @@
       "version": "1.5.0",
       "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
       "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
+      "dev": true,
       "license": "MIT"
     },
     "node_modules/@jridgewell/trace-mapping": {
       "version": "0.3.25",
       "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
       "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
+      "dev": true,
       "license": "MIT",
       "dependencies": {
         "@jridgewell/resolve-uri": "^3.1.0",
@@ -1377,6 +1420,7 @@
       "version": "0.11.0",
       "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
       "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
+      "dev": true,
       "license": "MIT",
       "optional": true,
       "engines": {
@@ -2294,6 +2338,7 @@
       "cpu": [
         "arm"
       ],
+      "dev": true,
       "license": "MIT",
       "optional": true,
       "os": [
@@ -2307,6 +2352,7 @@
       "cpu": [
         "arm64"
       ],
+      "dev": true,
       "license": "MIT",
       "optional": true,
       "os": [
@@ -2320,6 +2366,7 @@
       "cpu": [
         "arm64"
       ],
+      "dev": true,
       "license": "MIT",
       "optional": true,
       "os": [
@@ -2333,6 +2380,7 @@
       "cpu": [
         "x64"
       ],
+      "dev": true,
       "license": "MIT",
       "optional": true,
       "os": [
@@ -2346,6 +2394,7 @@
       "cpu": [
         "arm64"
       ],
+      "dev": true,
       "license": "MIT",
       "optional": true,
       "os": [
@@ -2359,6 +2408,7 @@
       "cpu": [
         "x64"
       ],
+      "dev": true,
       "license": "MIT",
       "optional": true,
       "os": [
@@ -2372,6 +2422,7 @@
       "cpu": [
         "arm"
       ],
+      "dev": true,
       "license": "MIT",
       "optional": true,
       "os": [
@@ -2385,6 +2436,7 @@
       "cpu": [
         "arm"
       ],
+      "dev": true,
       "license": "MIT",
       "optional": true,
       "os": [
@@ -2398,6 +2450,7 @@
       "cpu": [
         "arm64"
       ],
+      "dev": true,
       "license": "MIT",
       "optional": true,
       "os": [
@@ -2411,6 +2464,7 @@
       "cpu": [
         "arm64"
       ],
+      "dev": true,
       "license": "MIT",
       "optional": true,
       "os": [
@@ -2424,6 +2478,7 @@
       "cpu": [
         "loong64"
       ],
+      "dev": true,
       "license": "MIT",
       "optional": true,
       "os": [
@@ -2437,6 +2492,7 @@
       "cpu": [
         "ppc64"
       ],
+      "dev": true,
       "license": "MIT",
       "optional": true,
       "os": [
@@ -2450,6 +2506,7 @@
       "cpu": [
         "riscv64"
       ],
+      "dev": true,
       "license": "MIT",
       "optional": true,
       "os": [
@@ -2463,6 +2520,7 @@
       "cpu": [
         "s390x"
       ],
+      "dev": true,
       "license": "MIT",
       "optional": true,
       "os": [
@@ -2476,6 +2534,7 @@
       "cpu": [
         "x64"
       ],
+      "dev": true,
       "license": "MIT",
       "optional": true,
       "os": [
@@ -2489,6 +2548,7 @@
       "cpu": [
         "x64"
       ],
+      "dev": true,
       "license": "MIT",
       "optional": true,
       "os": [
@@ -2502,6 +2562,7 @@
       "cpu": [
         "arm64"
       ],
+      "dev": true,
       "license": "MIT",
       "optional": true,
       "os": [
@@ -2515,6 +2576,7 @@
       "cpu": [
         "ia32"
       ],
+      "dev": true,
       "license": "MIT",
       "optional": true,
       "os": [
@@ -2528,6 +2590,7 @@
       "cpu": [
         "x64"
       ],
+      "dev": true,
       "license": "MIT",
       "optional": true,
       "os": [
@@ -2757,12 +2820,14 @@
       "version": "1.0.6",
       "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
       "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==",
+      "dev": true,
       "license": "MIT"
     },
     "node_modules/@types/json-schema": {
       "version": "7.0.15",
       "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
       "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
+      "dev": true,
       "license": "MIT"
     },
     "node_modules/@types/node": {
@@ -2788,7 +2853,7 @@
       "version": "19.0.2",
       "resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.2.tgz",
       "integrity": "sha512-USU8ZI/xyKJwFTpjSVIrSeHBVAGagkHQKPNbxeWwql/vDmnTIBgx+TJnhFnj1NXgz8XfprU0egV2dROLGpsBEg==",
-      "devOptional": true,
+      "dev": true,
       "license": "MIT",
       "dependencies": {
         "csstype": "^3.0.2"
@@ -2798,7 +2863,7 @@
       "version": "19.0.2",
       "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.0.2.tgz",
       "integrity": "sha512-c1s+7TKFaDRRxr1TxccIX2u7sfCnc3RxkVyBIUA2lCpyqCF+QoAwQ/CBg7bsMdVwP120HEH143VQezKtef5nCg==",
-      "devOptional": true,
+      "dev": true,
       "license": "MIT",
       "peerDependencies": {
         "@types/react": "^19.0.0"
@@ -3511,6 +3576,7 @@
       "version": "8.14.0",
       "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
       "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
+      "dev": true,
       "license": "MIT",
       "bin": {
         "acorn": "bin/acorn"
@@ -3523,6 +3589,7 @@
       "version": "5.3.2",
       "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
       "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+      "dev": true,
       "license": "MIT",
       "peerDependencies": {
         "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
@@ -3560,6 +3627,7 @@
       "version": "6.1.0",
       "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
       "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
+      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">=12"
@@ -3572,6 +3640,7 @@
       "version": "4.3.0",
       "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
       "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+      "dev": true,
       "license": "MIT",
       "dependencies": {
         "color-convert": "^2.0.1"
@@ -3587,12 +3656,14 @@
       "version": "1.3.0",
       "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
       "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
+      "dev": true,
       "license": "MIT"
     },
     "node_modules/anymatch": {
       "version": "3.1.3",
       "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
       "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+      "dev": true,
       "license": "ISC",
       "dependencies": {
         "normalize-path": "^3.0.0",
@@ -3606,12 +3677,14 @@
       "version": "5.0.2",
       "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
       "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
+      "dev": true,
       "license": "MIT"
     },
     "node_modules/argparse": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
       "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+      "dev": true,
       "license": "Python-2.0"
     },
     "node_modules/aria-hidden": {
@@ -3871,6 +3944,7 @@
       "version": "2.3.0",
       "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
       "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
+      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">=8"
@@ -4059,6 +4133,7 @@
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
       "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">=6"
@@ -4068,6 +4143,7 @@
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
       "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
+      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">= 6"
@@ -4115,6 +4191,7 @@
       "version": "4.1.2",
       "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
       "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+      "dev": true,
       "license": "MIT",
       "dependencies": {
         "ansi-styles": "^4.1.0",
@@ -4194,6 +4271,7 @@
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
       "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+      "dev": true,
       "license": "MIT",
       "dependencies": {
         "color-name": "~1.1.4"
@@ -4206,6 +4284,7 @@
       "version": "1.1.4",
       "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
       "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+      "dev": true,
       "license": "MIT"
     },
     "node_modules/combined-stream": {
@@ -4224,6 +4303,7 @@
       "version": "4.1.1",
       "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
       "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
+      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">= 6"
@@ -4353,6 +4433,7 @@
       "version": "7.0.6",
       "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
       "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+      "dev": true,
       "license": "MIT",
       "dependencies": {
         "path-key": "^3.1.0",
@@ -4367,6 +4448,7 @@
       "version": "2.0.2",
       "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
       "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+      "dev": true,
       "license": "ISC",
       "dependencies": {
         "isexe": "^2.0.0"
@@ -4382,6 +4464,7 @@
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
       "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
+      "dev": true,
       "license": "MIT",
       "bin": {
         "cssesc": "bin/cssesc"
@@ -4394,7 +4477,7 @@
       "version": "3.1.3",
       "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
       "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
-      "devOptional": true,
+      "dev": true,
       "license": "MIT"
     },
     "node_modules/data-view-buffer": {
@@ -4494,6 +4577,7 @@
       "version": "0.1.4",
       "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
       "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+      "dev": true,
       "license": "MIT"
     },
     "node_modules/define-data-property": {
@@ -4568,12 +4652,14 @@
       "version": "1.2.2",
       "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
       "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
+      "dev": true,
       "license": "Apache-2.0"
     },
     "node_modules/dlv": {
       "version": "1.1.3",
       "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
       "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
+      "dev": true,
       "license": "MIT"
     },
     "node_modules/doctrine": {
@@ -4619,6 +4705,7 @@
       "version": "0.2.0",
       "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
       "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
+      "dev": true,
       "license": "MIT"
     },
     "node_modules/ee-first": {
@@ -4638,6 +4725,7 @@
       "version": "9.2.2",
       "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
       "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
+      "dev": true,
       "license": "MIT"
     },
     "node_modules/encodeurl": {
@@ -4835,6 +4923,7 @@
       "version": "0.21.5",
       "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
       "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
+      "dev": true,
       "hasInstallScript": true,
       "license": "MIT",
       "bin": {
@@ -4889,6 +4978,7 @@
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
       "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">=10"
@@ -4901,6 +4991,7 @@
       "version": "9.17.0",
       "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.17.0.tgz",
       "integrity": "sha512-evtlNcpJg+cZLcnVKwsai8fExnqjGPicK7gnUtlNuzu+Fv9bI0aLpND5T44VLQtoMEnI57LoXO9XAkIXwohKrA==",
+      "dev": true,
       "license": "MIT",
       "dependencies": {
         "@eslint-community/eslint-utils": "^4.2.0",
@@ -5001,6 +5092,7 @@
       "version": "8.2.0",
       "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz",
       "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==",
+      "dev": true,
       "license": "BSD-2-Clause",
       "dependencies": {
         "esrecurse": "^4.3.0",
@@ -5029,6 +5121,7 @@
       "version": "6.12.6",
       "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
       "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+      "dev": true,
       "license": "MIT",
       "dependencies": {
         "fast-deep-equal": "^3.1.1",
@@ -5045,12 +5138,14 @@
       "version": "0.4.1",
       "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
       "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+      "dev": true,
       "license": "MIT"
     },
     "node_modules/espree": {
       "version": "10.3.0",
       "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz",
       "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==",
+      "dev": true,
       "license": "BSD-2-Clause",
       "dependencies": {
         "acorn": "^8.14.0",
@@ -5068,6 +5163,7 @@
       "version": "1.6.0",
       "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
       "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
+      "dev": true,
       "license": "BSD-3-Clause",
       "dependencies": {
         "estraverse": "^5.1.0"
@@ -5080,6 +5176,7 @@
       "version": "4.3.0",
       "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
       "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+      "dev": true,
       "license": "BSD-2-Clause",
       "dependencies": {
         "estraverse": "^5.2.0"
@@ -5256,12 +5353,14 @@
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
       "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+      "dev": true,
       "license": "MIT"
     },
     "node_modules/fast-levenshtein": {
       "version": "2.0.6",
       "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
       "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+      "dev": true,
       "license": "MIT"
     },
     "node_modules/fast-uri": {
@@ -5283,6 +5382,7 @@
       "version": "8.0.0",
       "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
       "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
+      "dev": true,
       "license": "MIT",
       "dependencies": {
         "flat-cache": "^4.0.0"
@@ -5340,6 +5440,7 @@
       "version": "5.0.0",
       "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
       "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+      "dev": true,
       "license": "MIT",
       "dependencies": {
         "locate-path": "^6.0.0",
@@ -5356,6 +5457,7 @@
       "version": "4.0.1",
       "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
       "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
+      "dev": true,
       "license": "MIT",
       "dependencies": {
         "flatted": "^3.2.9",
@@ -5369,6 +5471,7 @@
       "version": "3.3.2",
       "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz",
       "integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==",
+      "dev": true,
       "license": "ISC"
     },
     "node_modules/for-each": {
@@ -5384,6 +5487,7 @@
       "version": "3.3.0",
       "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz",
       "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==",
+      "dev": true,
       "license": "ISC",
       "dependencies": {
         "cross-spawn": "^7.0.0",
@@ -5480,6 +5584,7 @@
       "version": "2.3.3",
       "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
       "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+      "dev": true,
       "hasInstallScript": true,
       "license": "MIT",
       "optional": true,
@@ -5604,6 +5709,7 @@
       "version": "10.4.5",
       "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
       "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
+      "dev": true,
       "license": "ISC",
       "dependencies": {
         "foreground-child": "^3.1.0",
@@ -5624,6 +5730,7 @@
       "version": "6.0.2",
       "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
       "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+      "dev": true,
       "license": "ISC",
       "dependencies": {
         "is-glob": "^4.0.3"
@@ -5636,6 +5743,7 @@
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
       "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+      "dev": true,
       "license": "MIT",
       "dependencies": {
         "balanced-match": "^1.0.0"
@@ -5645,6 +5753,7 @@
       "version": "9.0.5",
       "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
       "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+      "dev": true,
       "license": "ISC",
       "dependencies": {
         "brace-expansion": "^2.0.1"
@@ -5748,6 +5857,7 @@
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
       "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">=8"
@@ -5892,6 +6002,7 @@
       "version": "3.3.0",
       "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
       "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
+      "dev": true,
       "license": "MIT",
       "dependencies": {
         "parent-module": "^1.0.0",
@@ -5908,6 +6019,7 @@
       "version": "0.1.4",
       "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
       "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">=0.8.19"
@@ -5993,6 +6105,7 @@
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
       "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+      "dev": true,
       "license": "MIT",
       "dependencies": {
         "binary-extensions": "^2.0.0"
@@ -6112,6 +6225,7 @@
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
       "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">=8"
@@ -6347,6 +6461,7 @@
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
       "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+      "dev": true,
       "license": "ISC"
     },
     "node_modules/iterator.prototype": {
@@ -6370,6 +6485,7 @@
       "version": "3.4.3",
       "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
       "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
+      "dev": true,
       "license": "BlueOak-1.0.0",
       "dependencies": {
         "@isaacs/cliui": "^8.0.2"
@@ -6385,6 +6501,7 @@
       "version": "1.21.7",
       "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz",
       "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
+      "dev": true,
       "license": "MIT",
       "bin": {
         "jiti": "bin/jiti.js"
@@ -6400,6 +6517,7 @@
       "version": "4.1.0",
       "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
       "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+      "dev": true,
       "license": "MIT",
       "dependencies": {
         "argparse": "^2.0.1"
@@ -6425,6 +6543,7 @@
       "version": "3.0.1",
       "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
       "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+      "dev": true,
       "license": "MIT"
     },
     "node_modules/json-parse-even-better-errors": {
@@ -6453,6 +6572,7 @@
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
       "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+      "dev": true,
       "license": "MIT"
     },
     "node_modules/json5": {
@@ -6500,6 +6620,7 @@
       "version": "4.5.4",
       "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
       "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+      "dev": true,
       "license": "MIT",
       "dependencies": {
         "json-buffer": "3.0.1"
@@ -6509,6 +6630,7 @@
       "version": "0.4.1",
       "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
       "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+      "dev": true,
       "license": "MIT",
       "dependencies": {
         "prelude-ls": "^1.2.1",
@@ -6522,6 +6644,7 @@
       "version": "3.1.3",
       "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
       "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==",
+      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">=14"
@@ -6534,12 +6657,14 @@
       "version": "1.2.4",
       "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
       "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
+      "dev": true,
       "license": "MIT"
     },
     "node_modules/locate-path": {
       "version": "6.0.0",
       "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
       "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+      "dev": true,
       "license": "MIT",
       "dependencies": {
         "p-locate": "^5.0.0"
@@ -6562,6 +6687,7 @@
       "version": "4.6.2",
       "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
       "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+      "dev": true,
       "license": "MIT"
     },
     "node_modules/loose-envify": {
@@ -6728,6 +6854,7 @@
       "version": "7.1.2",
       "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
       "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
+      "dev": true,
       "license": "ISC",
       "engines": {
         "node": ">=16 || 14 >=14.17"
@@ -6786,6 +6913,7 @@
       "version": "2.7.0",
       "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
       "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
+      "dev": true,
       "license": "MIT",
       "dependencies": {
         "any-promise": "^1.0.0",
@@ -6797,6 +6925,7 @@
       "version": "3.3.8",
       "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
       "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
+      "dev": true,
       "funding": [
         {
           "type": "github",
@@ -6892,6 +7021,7 @@
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
       "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">=0.10.0"
@@ -6975,6 +7105,7 @@
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
       "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
+      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">= 6"
@@ -7147,6 +7278,7 @@
       "version": "0.9.4",
       "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
       "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
+      "dev": true,
       "license": "MIT",
       "dependencies": {
         "deep-is": "^0.1.3",
@@ -7164,6 +7296,7 @@
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
       "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+      "dev": true,
       "license": "MIT",
       "dependencies": {
         "yocto-queue": "^0.1.0"
@@ -7179,6 +7312,7 @@
       "version": "5.0.0",
       "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
       "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+      "dev": true,
       "license": "MIT",
       "dependencies": {
         "p-limit": "^3.0.2"
@@ -7194,6 +7328,7 @@
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
       "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
+      "dev": true,
       "license": "BlueOak-1.0.0"
     },
     "node_modules/pako": {
@@ -7207,6 +7342,7 @@
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
       "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+      "dev": true,
       "license": "MIT",
       "dependencies": {
         "callsites": "^3.0.0"
@@ -7228,6 +7364,7 @@
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
       "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">=8"
@@ -7237,6 +7374,7 @@
       "version": "3.1.1",
       "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
       "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">=8"
@@ -7252,6 +7390,7 @@
       "version": "1.11.1",
       "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
       "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
+      "dev": true,
       "license": "BlueOak-1.0.0",
       "dependencies": {
         "lru-cache": "^10.2.0",
@@ -7268,6 +7407,7 @@
       "version": "10.4.3",
       "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
       "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
+      "dev": true,
       "license": "ISC"
     },
     "node_modules/path-to-regexp": {
@@ -7309,6 +7449,7 @@
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
       "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+      "dev": true,
       "license": "ISC"
     },
     "node_modules/picomatch": {
@@ -7327,6 +7468,7 @@
       "version": "2.3.0",
       "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
       "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
+      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">=0.10.0"
@@ -7336,6 +7478,7 @@
       "version": "4.0.6",
       "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz",
       "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==",
+      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">= 6"
@@ -7354,6 +7497,7 @@
       "version": "8.4.49",
       "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz",
       "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==",
+      "dev": true,
       "funding": [
         {
           "type": "opencollective",
@@ -7382,6 +7526,7 @@
       "version": "15.1.0",
       "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz",
       "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==",
+      "dev": true,
       "license": "MIT",
       "dependencies": {
         "postcss-value-parser": "^4.0.0",
@@ -7399,6 +7544,7 @@
       "version": "1.22.10",
       "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
       "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
+      "dev": true,
       "license": "MIT",
       "dependencies": {
         "is-core-module": "^2.16.0",
@@ -7419,6 +7565,7 @@
       "version": "4.0.1",
       "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz",
       "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==",
+      "dev": true,
       "license": "MIT",
       "dependencies": {
         "camelcase-css": "^2.0.1"
@@ -7438,6 +7585,7 @@
       "version": "4.0.2",
       "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz",
       "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==",
+      "dev": true,
       "funding": [
         {
           "type": "opencollective",
@@ -7473,6 +7621,7 @@
       "version": "6.2.0",
       "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz",
       "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==",
+      "dev": true,
       "funding": [
         {
           "type": "opencollective",
@@ -7498,6 +7647,7 @@
       "version": "6.1.2",
       "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
       "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
+      "dev": true,
       "license": "MIT",
       "dependencies": {
         "cssesc": "^3.0.0",
@@ -7511,12 +7661,14 @@
       "version": "4.2.0",
       "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
       "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
+      "dev": true,
       "license": "MIT"
     },
     "node_modules/prelude-ls": {
       "version": "1.2.1",
       "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
       "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">= 0.8.0"
@@ -7706,6 +7858,7 @@
       "version": "2.3.1",
       "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
       "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">=6"
@@ -7978,6 +8131,7 @@
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
       "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
+      "dev": true,
       "license": "MIT",
       "dependencies": {
         "pify": "^2.3.0"
@@ -8090,6 +8244,7 @@
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
       "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">=4"
@@ -8119,6 +8274,7 @@
       "version": "4.29.0",
       "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.29.0.tgz",
       "integrity": "sha512-pdftUn12oB9Qlka+Vpyc39R28D4NsP9Sz6neepSrekofJmWzPD1sxcSO9hEOxFF8+7Kz3sHvwSkkRREI28M1/w==",
+      "dev": true,
       "license": "MIT",
       "dependencies": {
         "@types/estree": "1.0.6"
@@ -8373,6 +8529,7 @@
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
       "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+      "dev": true,
       "license": "MIT",
       "dependencies": {
         "shebang-regex": "^3.0.0"
@@ -8385,6 +8542,7 @@
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
       "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">=8"
@@ -8473,6 +8631,7 @@
       "version": "4.1.0",
       "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
       "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
+      "dev": true,
       "license": "ISC",
       "engines": {
         "node": ">=14"
@@ -8506,6 +8665,7 @@
       "version": "1.2.1",
       "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
       "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+      "dev": true,
       "license": "BSD-3-Clause",
       "engines": {
         "node": ">=0.10.0"
@@ -8614,6 +8774,7 @@
       "version": "5.1.2",
       "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
       "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
+      "dev": true,
       "license": "MIT",
       "dependencies": {
         "eastasianwidth": "^0.2.0",
@@ -8632,6 +8793,7 @@
       "version": "4.2.3",
       "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
       "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+      "dev": true,
       "license": "MIT",
       "dependencies": {
         "emoji-regex": "^8.0.0",
@@ -8646,6 +8808,7 @@
       "version": "5.0.1",
       "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
       "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">=8"
@@ -8655,12 +8818,14 @@
       "version": "8.0.0",
       "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
       "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+      "dev": true,
       "license": "MIT"
     },
     "node_modules/string-width-cjs/node_modules/strip-ansi": {
       "version": "6.0.1",
       "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
       "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+      "dev": true,
       "license": "MIT",
       "dependencies": {
         "ansi-regex": "^5.0.1"
@@ -8766,6 +8931,7 @@
       "version": "7.1.0",
       "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
       "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
+      "dev": true,
       "license": "MIT",
       "dependencies": {
         "ansi-regex": "^6.0.1"
@@ -8782,6 +8948,7 @@
       "version": "6.0.1",
       "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
       "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+      "dev": true,
       "license": "MIT",
       "dependencies": {
         "ansi-regex": "^5.0.1"
@@ -8794,6 +8961,7 @@
       "version": "5.0.1",
       "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
       "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">=8"
@@ -8803,6 +8971,7 @@
       "version": "3.1.1",
       "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
       "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">=8"
@@ -8815,6 +8984,7 @@
       "version": "3.35.0",
       "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz",
       "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==",
+      "dev": true,
       "license": "MIT",
       "dependencies": {
         "@jridgewell/gen-mapping": "^0.3.2",
@@ -8837,6 +9007,7 @@
       "version": "7.2.0",
       "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
       "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+      "dev": true,
       "license": "MIT",
       "dependencies": {
         "has-flag": "^4.0.0"
@@ -8871,6 +9042,7 @@
       "version": "3.4.17",
       "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz",
       "integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==",
+      "dev": true,
       "license": "MIT",
       "dependencies": {
         "@alloc/quick-lru": "^5.2.0",
@@ -8917,6 +9089,7 @@
       "version": "3.6.0",
       "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
       "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
+      "dev": true,
       "license": "MIT",
       "dependencies": {
         "anymatch": "~3.1.2",
@@ -8941,6 +9114,7 @@
       "version": "5.1.2",
       "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
       "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+      "dev": true,
       "license": "ISC",
       "dependencies": {
         "is-glob": "^4.0.1"
@@ -8953,6 +9127,7 @@
       "version": "3.6.0",
       "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
       "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+      "dev": true,
       "license": "MIT",
       "dependencies": {
         "picomatch": "^2.2.1"
@@ -8965,6 +9140,7 @@
       "version": "1.22.10",
       "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
       "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
+      "dev": true,
       "license": "MIT",
       "dependencies": {
         "is-core-module": "^2.16.0",
@@ -8985,6 +9161,7 @@
       "version": "3.3.1",
       "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
       "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
+      "dev": true,
       "license": "MIT",
       "dependencies": {
         "any-promise": "^1.0.0"
@@ -8994,6 +9171,7 @@
       "version": "1.6.0",
       "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
       "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
+      "dev": true,
       "license": "MIT",
       "dependencies": {
         "thenify": ">= 3.1.0 < 4"
@@ -9100,6 +9278,7 @@
       "version": "0.1.13",
       "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
       "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
+      "dev": true,
       "license": "Apache-2.0"
     },
     "node_modules/tsconfck": {
@@ -9139,6 +9318,7 @@
       "version": "0.4.0",
       "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
       "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+      "dev": true,
       "license": "MIT",
       "dependencies": {
         "prelude-ls": "^1.2.1"
@@ -9238,6 +9418,7 @@
       "version": "5.7.2",
       "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz",
       "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==",
+      "dev": true,
       "license": "Apache-2.0",
       "bin": {
         "tsc": "bin/tsc",
@@ -9432,6 +9613,7 @@
       "version": "4.4.1",
       "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
       "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+      "dev": true,
       "license": "BSD-2-Clause",
       "dependencies": {
         "punycode": "^2.1.0"
@@ -9493,6 +9675,7 @@
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
       "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+      "dev": true,
       "license": "MIT"
     },
     "node_modules/utils-merge": {
@@ -9566,6 +9749,7 @@
       "version": "5.4.11",
       "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz",
       "integrity": "sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==",
+      "dev": true,
       "license": "MIT",
       "dependencies": {
         "esbuild": "^0.21.3",
@@ -9941,6 +10125,7 @@
       "version": "1.2.5",
       "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
       "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
+      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">=0.10.0"
@@ -9950,6 +10135,7 @@
       "version": "8.1.0",
       "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
       "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
+      "dev": true,
       "license": "MIT",
       "dependencies": {
         "ansi-styles": "^6.1.0",
@@ -9968,6 +10154,7 @@
       "version": "7.0.0",
       "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
       "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+      "dev": true,
       "license": "MIT",
       "dependencies": {
         "ansi-styles": "^4.0.0",
@@ -9985,6 +10172,7 @@
       "version": "5.0.1",
       "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
       "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">=8"
@@ -9994,12 +10182,14 @@
       "version": "8.0.0",
       "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
       "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+      "dev": true,
       "license": "MIT"
     },
     "node_modules/wrap-ansi-cjs/node_modules/string-width": {
       "version": "4.2.3",
       "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
       "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+      "dev": true,
       "license": "MIT",
       "dependencies": {
         "emoji-regex": "^8.0.0",
@@ -10014,6 +10204,7 @@
       "version": "6.0.1",
       "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
       "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+      "dev": true,
       "license": "MIT",
       "dependencies": {
         "ansi-regex": "^5.0.1"
@@ -10026,6 +10217,7 @@
       "version": "6.2.1",
       "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
       "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
+      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">=12"
@@ -10062,6 +10254,7 @@
       "version": "2.6.1",
       "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.1.tgz",
       "integrity": "sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg==",
+      "dev": true,
       "license": "ISC",
       "bin": {
         "yaml": "bin.mjs"
@@ -10074,6 +10267,7 @@
       "version": "0.1.0",
       "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
       "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">=10"


Test patch: