diff --git a/docs/app/global.css b/docs/app/global.css index ac4955f41..5b5ce27a8 100644 --- a/docs/app/global.css +++ b/docs/app/global.css @@ -152,3 +152,11 @@ body { padding-top: 4rem !important; } } + +.paste-loading { + display: flex; + align-items: center; + justify-content: center; + color: var(--openui-text-neutral-secondary); + height: 100vh; +} diff --git a/docs/app/paste/components/Banner.tsx b/docs/app/paste/components/Banner.tsx new file mode 100644 index 000000000..e5bb4ab60 --- /dev/null +++ b/docs/app/paste/components/Banner.tsx @@ -0,0 +1,17 @@ +"use client"; + +import { Callout } from "@openuidev/react-ui"; + +export function Banner({ + tone, + children, +}: { + tone: "warning" | "danger" | "info"; + children: React.ReactNode; +}) { + return ( +
+ +
+ ); +} diff --git a/docs/app/paste/components/EditorPane.tsx b/docs/app/paste/components/EditorPane.tsx new file mode 100644 index 000000000..1a8971f47 --- /dev/null +++ b/docs/app/paste/components/EditorPane.tsx @@ -0,0 +1,76 @@ +"use client"; + +import { EditorView } from "@codemirror/view"; +import CodeMirror from "@uiw/react-codemirror"; +import { useMemo } from "react"; +import { useTheme } from "next-themes"; +import { componentHoverTooltip, type ComponentDefs } from "@paste/lib/editor/hover"; +import { openuiLang } from "@paste/lib/editor/openui-lang"; + +const editorTheme = EditorView.theme({ + "&": { + height: "100%", + fontSize: "14px", + backgroundColor: "transparent", + }, + ".cm-content": { + fontFamily: "var(--openui-font-code, ui-monospace, SFMono-Regular, monospace)", + caretColor: "var(--openui-text-neutral-primary, currentColor)", + }, + ".cm-gutters": { + backgroundColor: "transparent", + color: "var(--openui-text-neutral-tertiary, #888)", + border: "none", + }, + "&.cm-focused": { outline: "none" }, + ".cm-activeLine": { backgroundColor: "var(--openui-sunk, rgba(0,0,0,0.03))" }, + ".cm-activeLineGutter": { backgroundColor: "transparent" }, +}); + +export function EditorPane({ + code, + onChange, + readOnly, + componentDefs, +}: { + code: string; + onChange: (code: string) => void; + readOnly: boolean; + /** Active library's schema $defs — powers hover signatures. */ + componentDefs: ComponentDefs | null; +}) { + const extensions = useMemo( + () => [ + openuiLang, + editorTheme, + EditorView.lineWrapping, + componentHoverTooltip(() => componentDefs), + ], + [componentDefs], + ); + // Docs theming is class-based via next-themes, not prefers-color-scheme. + const dark = useTheme().resolvedTheme === "dark"; + return ( +
+ + {readOnly &&
read-only during playback
} +
+ ); +} diff --git a/docs/app/paste/components/HelpDialog.tsx b/docs/app/paste/components/HelpDialog.tsx new file mode 100644 index 000000000..d0a3114a5 --- /dev/null +++ b/docs/app/paste/components/HelpDialog.tsx @@ -0,0 +1,111 @@ +"use client"; + +import { Button } from "@openuidev/react-ui"; +import { HelpCircle, X } from "lucide-react"; +import { useEffect, useState } from "react"; + +export function HelpDialog() { + const [open, setOpen] = useState(false); + + useEffect(() => { + if (!open) return; + const onKey = (e: KeyboardEvent) => { + if (e.key === "Escape") setOpen(false); + }; + window.addEventListener("keydown", onKey); + return () => window.removeEventListener("keydown", onKey); + }, [open]); + + return ( + <> + + {open && ( +
setOpen(false)}> +
e.stopPropagation()} + > +
+

How to use Paste

+
+ +
+

+ Paste is a playground for OpenUI Lang. Paste code on the left; the + panels on the right show what the parser and renderer make of it. +

+ +

Quick start

+
    +
  1. + Pick an example from Examples, or paste your own Lang code +
  2. +
  3. + Validation runs as you type against the selected lang-core{" "} + version. Every published npm version is available and loaded on demand. +
  4. +
  5. + Switch the component Library (openui, openui chat) to validate + and render against a different component set. +
  6. +
+ +

Panels

+
    +
  • + Render: live output. Interactive: forms hold state, and{" "} + Query() calls resolve with mocked data (any tool name returns sample + data; calls are logged under the canvas). Always rendered with the bundled + renderer; the selected lang-core version drives the other panels. +
  • +
  • + Validation: errors grouped by code, with fix hints, plus + unresolved refs and orphaned statements. +
  • +
  • + Tree: the parsed element tree, state declarations, queries and + mutations. +
  • +
  • + JSON: the raw ParseResult. +
  • +
  • + Stream: a per-chunk trace of the streaming parse with a final + streaming-vs-one-shot convergence check. +
  • +
+ +

Streaming mode

+

+ Stream replays your code as if an LLM were emitting it.{" "} + LLM-like chunking adds realistic jitter and stalls (reproducible via{" "} + Seed). Watch the render window build up live, pause and{" "} + Step chunk by chunk, and check whether the root ever appears or + drops mid-stream. +

+
+
+
+ )} + + ); +} diff --git a/docs/app/paste/components/Playground.tsx b/docs/app/paste/components/Playground.tsx new file mode 100644 index 000000000..2387a7ee5 --- /dev/null +++ b/docs/app/paste/components/Playground.tsx @@ -0,0 +1,189 @@ +"use client"; + +import { Tabs, TabsContent, TabsList, TabsTrigger, Tag } from "@openuidev/react-ui"; +import { useMemo, useState } from "react"; +import { Group, Panel, Separator } from "react-resizable-panels"; +import { useMediaQuery } from "@paste/hooks/useMediaQuery"; +import { useValidation } from "@paste/hooks/useValidation"; +import { useVersionList } from "@paste/hooks/useVersionList"; +import { DEFAULT_EXAMPLE } from "@paste/lib/examples"; +import { getRootName, getSchema, type LibraryId } from "@paste/lib/libraries"; +import { usePlayback } from "@paste/lib/streaming/usePlayback"; +import { BUNDLED_LANG_CORE_VERSION } from "@paste/lib/versions/loader"; +import { Banner } from "./Banner"; +import { EditorPane } from "./EditorPane"; +import { RenderErrorBoundary } from "./RenderErrorBoundary"; +import { JsonPanel } from "./panels/JsonPanel"; +import { RenderPanel } from "./panels/RenderPanel"; +import { StreamTimeline } from "./panels/StreamTimeline"; +import { TreePanel } from "./panels/TreePanel"; +import { ValidationPanel } from "./panels/ValidationPanel"; +import { Toasts, type ToastItem } from "./Toasts"; +import { Toolbar } from "./Toolbar"; + +type Tab = "render" | "validation" | "tree" | "json" | "stream"; + +const TABS: { id: Tab; label: string }[] = [ + { id: "render", label: "Render" }, + { id: "validation", label: "Validation" }, + { id: "tree", label: "Tree" }, + { id: "json", label: "JSON" }, + { id: "stream", label: "Stream" }, +]; + +const BIG_INPUT_BYTES = 200 * 1024; +const CHAR_STRATEGY_LIMIT = 50 * 1024; + +export function Playground() { + const [code, setCode] = useState(DEFAULT_EXAMPLE.code); + const [libraryId, setLibraryId] = useState("openui"); + // null = "no explicit pick yet" → follow the registry's latest tag. + const [pickedVersion, setPickedVersion] = useState(null); + const [tab, setTab] = useState("render"); + + const versions = useVersionList(); + const version = pickedVersion ?? versions.list.latest ?? BUNDLED_LANG_CORE_VERSION; + const { outcome, loaded, loadingVersion } = useValidation(code, version, libraryId); + + const schema = useMemo(() => getSchema(libraryId), [libraryId]); + const componentDefs = useMemo( + () => (schema as { $defs?: import("@paste/lib/editor/hover").ComponentDefs })?.$defs ?? null, + [schema], + ); + const rootName = getRootName(libraryId); + const playback = usePlayback(code, loaded, schema, rootName); + + const playbackActive = playback.state.status === "playing" || playback.state.status === "paused"; + const isStreaming = playback.state.status === "playing"; + // Editor stacks above the output on narrow screens (split flips vertical). + const narrow = useMediaQuery("(max-width: 860px)"); + + // Streaming playback takes over all panels while it owns the parse state. + const displayed = playbackActive || playback.state.status === "done" + ? { result: playback.state.result, enriched: null, fatal: playback.state.fatal } + : outcome; + const renderedCode = playbackActive ? playback.state.prefix : code; + + const changeCode = (next: string) => { + if (playback.state.status !== "idle") playback.reset(); + setCode(next); + }; + + const loadExample = (exampleCode: string) => { + changeCode(exampleCode); + }; + + const toasts: ToastItem[] = []; + if (versions.loading) + toasts.push({ id: "versions", text: "Fetching lang-core versions from npm…", spinning: true }); + if (loadingVersion) + toasts.push({ id: "load", text: `Loading lang-core ${version} from CDN…`, spinning: true }); + + return ( +
+ { + if (playback.state.status !== "idle") playback.reset(); + setLibraryId(id); + }} + version={version} + onVersionChange={(v) => { + if (playback.state.status !== "idle") playback.reset(); + setPickedVersion(v); + }} + versions={versions} + loaded={loaded} + onLoadExample={loadExample} + playback={playback} + bigInput={code.length > CHAR_STRATEGY_LIMIT} + /> + + {loaded && !loaded.compatible && ( + + lang-core {loaded.version} is incompatible with this playground ({loaded.reason}) — + validation is running on the bundled {BUNDLED_LANG_CORE_VERSION} instead. + + )} + {loaded && loaded.compatible && loaded.source === "bundled" && version !== BUNDLED_LANG_CORE_VERSION && ( + + Could not load lang-core {version} from a CDN — validating with the bundled{" "} + {BUNDLED_LANG_CORE_VERSION}. + + )} + {code.length > BIG_INPUT_BYTES && ( + + Large input ({Math.round(code.length / 1024)} KB) — validation may feel sluggish. + + )} + + + {/* v4: unitless strings are percentages; numbers would mean pixels */} + + + + + + +
+ setTab(v as Tab)} + className="output-tabs" + variant="clear" + > +
+ + {TABS.map((t) => ( + + ))} + + {loadingVersion && } +
+ + + + + + + + + + + + + + + +
+
+
+
+
+ +
+ ); +} diff --git a/docs/app/paste/components/RenderErrorBoundary.tsx b/docs/app/paste/components/RenderErrorBoundary.tsx new file mode 100644 index 000000000..cddd30b75 --- /dev/null +++ b/docs/app/paste/components/RenderErrorBoundary.tsx @@ -0,0 +1,50 @@ +"use client"; + +import { Button } from "@openuidev/react-ui"; +import { Component, type ReactNode } from "react"; + +interface Props { + /** Bump to clear the error state (e.g. when the code or version changes). */ + resetKey: string; + /** Card headline, e.g. "Renderer crashed" or "Output panel crashed". */ + title?: string; + /** Extra context line shown under the error message. */ + hint?: string; + children: ReactNode; +} + +interface State { + error: Error | null; + lastResetKey: string; +} + +export class RenderErrorBoundary extends Component { + state: State = { error: null, lastResetKey: this.props.resetKey }; + + static getDerivedStateFromError(error: Error): Partial { + return { error }; + } + + static getDerivedStateFromProps(props: Props, state: State): Partial | null { + if (props.resetKey !== state.lastResetKey) { + return { error: null, lastResetKey: props.resetKey }; + } + return null; + } + + render() { + if (this.state.error) { + return ( +
+ {this.props.title ?? "Renderer crashed"} +
{this.state.error.message}
+ {this.props.hint &&

{this.props.hint}

} + +
+ ); + } + return this.props.children; + } +} diff --git a/docs/app/paste/components/StreamControls.tsx b/docs/app/paste/components/StreamControls.tsx new file mode 100644 index 000000000..a1422b891 --- /dev/null +++ b/docs/app/paste/components/StreamControls.tsx @@ -0,0 +1,165 @@ +"use client"; + +import { + Button, + Input, + Label, + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@openuidev/react-ui"; +import { Pause, Play, RotateCcw, StepForward } from "lucide-react"; +import { CHUNK_STRATEGIES, type ChunkStrategy } from "@paste/lib/streaming/chunker"; +import type { PlaybackControls } from "@paste/lib/streaming/usePlayback"; + +const SPEEDS = ["0.25", "0.5", "1", "2", "4", "8"]; +const ICON = { size: 14 }; + +/** + * Split so the toolbar can compose them differently per breakpoint: buttons + * stay inline on mobile while the settings fields move into the drawer. + * strategy/seed state lives in Toolbar for the same reason. + */ +export interface StreamSettings { + strategy: ChunkStrategy; + onStrategyChange: (s: ChunkStrategy) => void; + seed: number; + onSeedChange: (n: number) => void; +} + +export function PlaybackButtons({ + playback, + settings, +}: { + playback: PlaybackControls; + settings: StreamSettings; +}) { + const { state, start, pause, resume, step, reset } = playback; + return ( + <> +
+ {state.status === "playing" ? ( + + ) : state.status === "paused" ? ( + + ) : ( + + )} + + +
+ {state.totalChunks > 0 && ( + + {state.chunkIndex}/{state.totalChunks} + {state.emulated && " · emulated"} + + )} + + ); +} + +export function StreamSettingsFields({ + playback, + bigInput, + settings, +}: { + playback: PlaybackControls; + bigInput: boolean; + settings: StreamSettings; +}) { + const { state, setSpeed, speed } = playback; + const active = state.status === "playing" || state.status === "paused"; + const { strategy, onStrategyChange, seed, onSeedChange } = settings; + + return ( + <> +
+ + +
+ +
+ + +
+ +
+ + onSeedChange(Number(e.target.value) || 0)} + disabled={active || strategy !== "llm"} + /> +
+ + ); +} diff --git a/docs/app/paste/components/Toasts.tsx b/docs/app/paste/components/Toasts.tsx new file mode 100644 index 000000000..e87973b8b --- /dev/null +++ b/docs/app/paste/components/Toasts.tsx @@ -0,0 +1,34 @@ +"use client"; + +import { Callout } from "@openuidev/react-ui"; + +export interface ToastItem { + id: string; + text: string; + /** Loading toasts get a spinner; they disappear when the state resolves. */ + spinning?: boolean; +} + +/** + * Purely derived toast stack — callers pass the currently active long-running + * tasks and the stack reflects them; no timers or imperative queue needed. + */ +export function Toasts({ items }: { items: ToastItem[] }) { + if (items.length === 0) return null; + return ( +
+ {items.map((t) => ( + + {t.spinning && } + {t.text} + + } + /> + ))} +
+ ); +} diff --git a/docs/app/paste/components/Toolbar.tsx b/docs/app/paste/components/Toolbar.tsx new file mode 100644 index 000000000..7861b4a7b --- /dev/null +++ b/docs/app/paste/components/Toolbar.tsx @@ -0,0 +1,214 @@ +"use client"; + +import { + Button, + Label, + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, + Tag, +} from "@openuidev/react-ui"; +import { ArrowLeft, SlidersHorizontal, X } from "lucide-react"; +import Link from "next/link"; +import { useEffect, useState } from "react"; +import { useMediaQuery } from "@paste/hooks/useMediaQuery"; +import type { VersionListState } from "@paste/hooks/useVersionList"; +import { EXAMPLES } from "@paste/lib/examples"; +import { LIBRARIES, type LibraryId } from "@paste/lib/libraries"; +import type { ChunkStrategy } from "@paste/lib/streaming/chunker"; +import type { PlaybackControls } from "@paste/lib/streaming/usePlayback"; +import type { LoadedLangCore } from "@paste/lib/versions/types"; +import { HelpDialog } from "./HelpDialog"; +import { PlaybackButtons, StreamSettingsFields } from "./StreamControls"; +// The docs site's own toggle (animated sun/moon, phosphor icons) — same one +// as the homepage header, so /paste matches the rest of the site. +import { ThemeToggle } from "@/components/theme-toggle"; +import { VersionPicker } from "./VersionPicker"; + +export function Toolbar({ + libraryId, + onLibraryChange, + version, + onVersionChange, + versions, + loaded, + onLoadExample, + playback, + bigInput, +}: { + libraryId: LibraryId; + onLibraryChange: (id: LibraryId) => void; + version: string; + onVersionChange: (v: string) => void; + versions: VersionListState; + loaded: LoadedLangCore | null; + onLoadExample: (code: string) => void; + playback: PlaybackControls; + bigInput: boolean; +}) { + const playbackActive = + playback.state.status === "playing" || playback.state.status === "paused"; + // strategy/seed live here (not in StreamControls) so the play button in the + // toolbar and the settings fields in the mobile drawer share them. + const [strategy, setStrategy] = useState("llm"); + const [seed, setSeed] = useState(42); + const settings = { + strategy, + onStrategyChange: setStrategy, + seed, + onSeedChange: setSeed, + }; + + const narrow = useMediaQuery("(max-width: 860px)"); + const [drawerOpen, setDrawerOpen] = useState(false); + + useEffect(() => { + if (!drawerOpen) return; + const onKey = (e: KeyboardEvent) => { + if (e.key === "Escape") setDrawerOpen(false); + }; + window.addEventListener("keydown", onKey); + return () => window.removeEventListener("keydown", onKey); + }, [drawerOpen]); + + const libraryField = ( +
+ + +
+ ); + + const examplesField = ( +
+ {!narrow && } + {/* value stays "" so the same example can be re-selected */} + +
+ ); + + const viaTag = + loaded && loaded.source !== "bundled" ? ( + + ) : null; + + return ( + <> +
+ + + Back to docs + +
+ OpenUI Paste + +
+
+
+
+ + {narrow ? ( + <> + {examplesField} + + + ) : ( + <> + + + {libraryField} + + {examplesField} + {viaTag} + + )} + +
+
+ {narrow && drawerOpen && ( +
setDrawerOpen(false)}> +
e.stopPropagation()} + > +
+

Settings

+ +
+
+ + {libraryField} + + {viaTag} +
+
+
+ )} + + ); +} diff --git a/docs/app/paste/components/VersionPicker.tsx b/docs/app/paste/components/VersionPicker.tsx new file mode 100644 index 000000000..1452c17c1 --- /dev/null +++ b/docs/app/paste/components/VersionPicker.tsx @@ -0,0 +1,56 @@ +"use client"; + +import { + Button, + Label, + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectLabel, + SelectTrigger, + SelectValue, +} from "@openuidev/react-ui"; +import type { VersionListState } from "@paste/hooks/useVersionList"; + +export function VersionPicker({ + value, + onChange, + versions, + disabled, +}: { + value: string; + onChange: (v: string) => void; + versions: VersionListState; + disabled?: boolean; +}) { + const { list, loading, error, retry } = versions; + return ( +
+ + + {error && ( + + )} +
+ ); +} diff --git a/docs/app/paste/components/panels/JsonPanel.tsx b/docs/app/paste/components/panels/JsonPanel.tsx new file mode 100644 index 000000000..2015e77b5 --- /dev/null +++ b/docs/app/paste/components/panels/JsonPanel.tsx @@ -0,0 +1,42 @@ +"use client"; + +import { Button } from "@openuidev/react-ui"; +import { useMemo, useState } from "react"; +import type { ParseResult } from "@paste/lib/versions/types"; + +const COLLAPSE_BYTES = 200 * 1024; + +export function JsonPanel({ result }: { result: ParseResult | null }) { + const [forceExpand, setForceExpand] = useState(false); + const json = useMemo(() => { + if (!result) return null; + try { + return JSON.stringify( + result, + (_k, v) => (typeof v === "function" ? `[function]` : v === undefined ? null : v), + 2, + ); + } catch (err) { + return `/* could not stringify: ${err instanceof Error ? err.message : String(err)} */`; + } + }, [result]); + + if (!json) return
No parse result yet.
; + if (json.length > COLLAPSE_BYTES && !forceExpand) { + return ( +
+

+ Result is {(json.length / 1024).toFixed(0)} KB.{" "} + +

+
+ ); + } + return ( +
+
{json}
+
+ ); +} diff --git a/docs/app/paste/components/panels/RenderPanel.tsx b/docs/app/paste/components/panels/RenderPanel.tsx new file mode 100644 index 000000000..9ce576977 --- /dev/null +++ b/docs/app/paste/components/panels/RenderPanel.tsx @@ -0,0 +1,131 @@ +"use client"; + +import { Renderer } from "@openuidev/react-lang"; +import { FlaskConical } from "lucide-react"; +import { useMemo, useState } from "react"; +import { getLibrary, type LibraryId } from "@paste/lib/libraries"; +import { createMockToolProvider, type MockToolCall } from "@paste/lib/mockTools"; +import { BUNDLED_LANG_CORE_VERSION } from "@paste/lib/versions/loader"; +import type { OpenUIError } from "@paste/lib/versions/types"; +import { RenderErrorBoundary } from "../RenderErrorBoundary"; + +const TOOL_CALL_LOG_CAP = 20; + +export function RenderPanel({ + code, + libraryId, + isStreaming, + selectedVersion, +}: { + code: string; + libraryId: LibraryId; + isStreaming: boolean; + selectedVersion: string; +}) { + const [runtimeErrors, setRuntimeErrors] = useState([]); + const [formState, setFormState] = useState>({}); + const [actionLog, setActionLog] = useState([]); + const [toolCalls, setToolCalls] = useState([]); + + // Stale logs from a previous snippet are misleading — reset when the code + // changes (render-time state adjustment, not an effect). Skipped while + // streaming, where `code` grows chunk by chunk and calls should accumulate. + const [lastCode, setLastCode] = useState(code); + if (code !== lastCode && !isStreaming) { + setLastCode(code); + setToolCalls([]); + setActionLog([]); + } + // Mounted with key={libraryId}, so switching library remounts the panel and + // clears stale runtime errors / form state / logs naturally. + const library = getLibrary(libraryId).library; + + const toolProvider = useMemo( + () => + createMockToolProvider((call) => + setToolCalls((log) => [...log.slice(-(TOOL_CALL_LOG_CAP - 1)), call]), + ), + [], + ); + + const usesTools = /\b(Query|Mutation)\s*\(/.test(code); + + return ( +
+ {selectedVersion !== BUNDLED_LANG_CORE_VERSION && ( +

+ Rendered with bundled lang-core {BUNDLED_LANG_CORE_VERSION} — validation uses v + {selectedVersion}. +

+ )} + {usesTools && ( +

+ + + Query() and Mutation() run against a{" "} + mock tool provider: any tool name resolves with sample data after + ~400ms. Calls are logged below the canvas. + +

+ )} +
+ + setActionLog((log) => [...log.slice(-19), safeDescribe(event)])} + /> + +
+ {toolCalls.length > 0 && ( +
+ Mock tool calls ({toolCalls.length}) +
    + {toolCalls.map((c, i) => ( +
  • + {c.tool}({safeDescribe(c.args)}) → sample data +
  • + ))} +
+
+ )} + {runtimeErrors.length > 0 && ( +
+ Runtime errors ({runtimeErrors.length}) +
    + {runtimeErrors.map((e, i) => ( +
  • + {e.code} {e.message} + {e.hint &&

    {e.hint}

    } +
  • + ))} +
+
+ )} + {actionLog.length > 0 && ( +
+ Actions ({actionLog.length}) +
{actionLog.join("\n")}
+
+ )} + {Object.keys(formState).length > 0 && ( +
+ Form state +
{JSON.stringify(formState, null, 2)}
+
+ )} +
+ ); +} + +function safeDescribe(event: unknown): string { + try { + return JSON.stringify(event); + } catch { + return String(event); + } +} diff --git a/docs/app/paste/components/panels/StreamTimeline.tsx b/docs/app/paste/components/panels/StreamTimeline.tsx new file mode 100644 index 000000000..6de57fa25 --- /dev/null +++ b/docs/app/paste/components/panels/StreamTimeline.tsx @@ -0,0 +1,92 @@ +"use client"; + +import type { PlaybackState } from "@paste/lib/streaming/usePlayback"; +import { CheckCircle2, Circle, CircleDot, CircleMinus, CirclePlus, XCircle } from "lucide-react"; + +export function StreamTimeline({ state }: { state: PlaybackState }) { + if (state.status === "idle") { + return ( +
+ Press Stream to replay the current code as simulated LLM output and watch + the parser converge chunk by chunk. +
+ ); + } + return ( +
+ {state.convergence && ( +
+ {state.convergence === "converged" ? ( + <> + Streaming result converged with one-shot parse + + ) : ( + <> + DIVERGED from one-shot parse — parser bug in this version? + + )} +
+ )} + {state.fatal && ( +
+ Streaming parse threw +
{state.fatal}
+
+ )} + {state.traceTruncated && ( +

Trace capped at 5,000 rows — remaining chunks not logged.

+ )} + + + + + + + + + + + + + + + {state.trace.map((row) => ( + + + + + + + + + + + ))} + +
#chunkdelayrootstmtsstateunreserrs
{row.i} + {visualize(row.chunkPreview)} + {row.delayMs}ms + {row.rootAppeared ? ( + <> + appeared + + ) : row.rootDropped ? ( + <> + dropped + + ) : row.rootPresent ? ( + + ) : ( + + )} + {row.statementCount}{row.incomplete ? "incomplete" : "complete"}{row.unresolvedCount}{row.errorCount}
+
+ ); +} + +function visualize(text: string): string { + return text.replace(/\n/g, "⏎").replace(/\t/g, "⇥"); +} diff --git a/docs/app/paste/components/panels/TreePanel.tsx b/docs/app/paste/components/panels/TreePanel.tsx new file mode 100644 index 000000000..33bad0838 --- /dev/null +++ b/docs/app/paste/components/panels/TreePanel.tsx @@ -0,0 +1,120 @@ +"use client"; + +import { Tag } from "@openuidev/react-ui"; +import { ChevronDown, ChevronRight } from "lucide-react"; +import { useState } from "react"; +import type { ParseResult } from "@paste/lib/versions/types"; + +function isElementNode(v: unknown): v is { type: "element"; typeName: string; props: Record; partial?: boolean; statementId?: string } { + return !!v && typeof v === "object" && (v as { type?: unknown }).type === "element"; +} + +/** AST / runtime-expression values render as ⟨…⟩ placeholders, like the harness. */ +function isExprLike(v: unknown): boolean { + if (!v || typeof v !== "object") return false; + const t = (v as { type?: unknown }).type; + return typeof t === "string" && t !== "element"; +} + +function PropValue({ value }: { value: unknown }) { + if (isElementNode(value)) return ; + if (Array.isArray(value)) { + if (value.some((v) => isElementNode(v))) { + return ( +
+ {value.map((v, i) => ( + + ))} +
+ ); + } + return {safeStringify(value)}; + } + if (isExprLike(value)) { + return ⟨{(value as { type: string }).type}⟩; + } + return {safeStringify(value)}; +} + +function safeStringify(v: unknown): string { + try { + const s = JSON.stringify(v); + return s && s.length > 120 ? `${s.slice(0, 120)}…` : (s ?? String(v)); + } catch { + return String(v); + } +} + +function NodeView({ node }: { node: { typeName: string; props: Record; partial?: boolean; statementId?: string } }) { + const [open, setOpen] = useState(true); + const entries = Object.entries(node.props ?? {}); + return ( +
+ + {open && entries.length > 0 && ( +
+ {entries.map(([k, v]) => ( +
+
{k}
+
+ +
+
+ ))} +
+ )} +
+ ); +} + +export function TreePanel({ result }: { result: ParseResult | null }) { + if (!result) return
No parse result yet.
; + const state = result.stateDeclarations ?? {}; + const queries = result.queryStatements ?? []; + const mutations = result.mutationStatements ?? []; + return ( +
+ {result.root ? ( + + ) : ( +
+ root is null — the renderer would show a blank UI for this input. +
+ )} + {Object.keys(state).length > 0 && ( +
+

State declarations

+
+ {Object.entries(state).map(([k, v]) => ( +
+
${k.replace(/^\$/, "")}
+
+ {safeStringify(v)} +
+
+ ))} +
+
+ )} + {queries.length > 0 && ( +
+

Queries ({queries.length})

+
{safeStringify(queries)}
+
+ )} + {mutations.length > 0 && ( +
+

Mutations ({mutations.length})

+
{safeStringify(mutations)}
+
+ )} +
+ ); +} diff --git a/docs/app/paste/components/panels/ValidationPanel.tsx b/docs/app/paste/components/panels/ValidationPanel.tsx new file mode 100644 index 000000000..77559ee93 --- /dev/null +++ b/docs/app/paste/components/panels/ValidationPanel.tsx @@ -0,0 +1,91 @@ +"use client"; + +import { Tag } from "@openuidev/react-ui"; +import { groupByCode } from "@paste/lib/groupErrors"; +import type { ValidationOutcome } from "@paste/lib/parse"; +import type { OpenUIError } from "@paste/lib/versions/types"; +import { CheckCircle2, Lightbulb } from "lucide-react"; + +function hintFor(enriched: OpenUIError[] | null, code: string, path: string, component: string) { + return enriched?.find((e) => e.code === code && e.path === path && e.component === component) + ?.hint; +} + +export function ValidationPanel({ outcome }: { outcome: ValidationOutcome }) { + const { result, enriched, fatal } = outcome; + + if (fatal) { + return ( +
+
+ Parser threw +
{fatal}
+

This can happen on old lang-core versions with a different API surface.

+
+
+ ); + } + if (!result) + return
Paste some OpenUI Lang code to validate it.
; + + const { meta } = result; + const groups = groupByCode(meta.errors); + + return ( +
+
+ + + + {meta.unresolved.map((u) => ( + + ))} + {meta.orphaned.map((o) => ( + + ))} +
+ + {meta.errors.length === 0 ? ( +
+ No validation errors +
+ ) : ( + [...groups.entries()].map(([code, errors]) => ( +
+

+ {code} ({errors.length}) +

+
    + {errors.map((e, i) => { + const hint = hintFor(enriched, e.code, e.path, e.component); + return ( +
  • +
    + {e.component} + {e.path} + {e.statementId && } +
    +

    {e.message}

    + {hint && ( +

    + {hint} +

    + )} +
  • + ); + })} +
+
+ )) + )} +
+ ); +} diff --git a/docs/app/paste/hooks/useMediaQuery.ts b/docs/app/paste/hooks/useMediaQuery.ts new file mode 100644 index 000000000..c8df6542c --- /dev/null +++ b/docs/app/paste/hooks/useMediaQuery.ts @@ -0,0 +1,16 @@ +"use client"; + +import { useSyncExternalStore } from "react"; + +/** Reactive matchMedia — SSR-safe (returns false on the server snapshot). */ +export function useMediaQuery(query: string): boolean { + return useSyncExternalStore( + (onChange) => { + const mql = window.matchMedia(query); + mql.addEventListener("change", onChange); + return () => mql.removeEventListener("change", onChange); + }, + () => window.matchMedia(query).matches, + () => false, + ); +} diff --git a/docs/app/paste/hooks/useValidation.ts b/docs/app/paste/hooks/useValidation.ts new file mode 100644 index 000000000..e17ebd337 --- /dev/null +++ b/docs/app/paste/hooks/useValidation.ts @@ -0,0 +1,63 @@ +"use client"; + +import { useEffect, useMemo, useState } from "react"; +import { getComponentNames, getRootName, getSchema, type LibraryId } from "@paste/lib/libraries"; +import { EMPTY_OUTCOME, runValidation, type ValidationOutcome } from "@paste/lib/parse"; +import { loadLangCore } from "@paste/lib/versions/loader"; +import type { LoadedLangCore } from "@paste/lib/versions/types"; + +export interface ValidationState { + outcome: ValidationOutcome; + loaded: LoadedLangCore | null; + /** True while the selected version is downloading from the CDN. */ + loadingVersion: boolean; +} + +const DEBOUNCE_MS = 150; + +function useDebounced(value: T, ms: number): T { + const [debounced, setDebounced] = useState(value); + useEffect(() => { + const timer = setTimeout(() => setDebounced(value), ms); + return () => clearTimeout(timer); + }, [value, ms]); + return debounced; +} + +/** + * Loads the selected lang-core version and derives debounced one-shot + * validation from code / version / library. + */ +export function useValidation(code: string, version: string, libraryId: LibraryId): ValidationState { + // Which version a load completed FOR (the loaded module may be a fallback + // with a different .version) — loading state is derived by comparison. + const [loadState, setLoadState] = useState<{ requested: string; loaded: LoadedLangCore } | null>( + null, + ); + + useEffect(() => { + let cancelled = false; + loadLangCore(version).then((loaded) => { + if (!cancelled) setLoadState({ requested: version, loaded }); + }); + return () => { + cancelled = true; + }; + }, [version]); + + const loaded = loadState?.requested === version ? loadState.loaded : null; + const debouncedCode = useDebounced(code, DEBOUNCE_MS); + + const outcome = useMemo(() => { + if (!loaded?.compatible) return EMPTY_OUTCOME; + return runValidation( + debouncedCode, + loaded, + getSchema(libraryId), + getRootName(libraryId), + getComponentNames(libraryId), + ); + }, [debouncedCode, loaded, libraryId]); + + return { outcome, loaded, loadingVersion: !loaded }; +} diff --git a/docs/app/paste/hooks/useVersionList.ts b/docs/app/paste/hooks/useVersionList.ts new file mode 100644 index 000000000..783328c71 --- /dev/null +++ b/docs/app/paste/hooks/useVersionList.ts @@ -0,0 +1,54 @@ +"use client"; + +import { useCallback, useEffect, useState } from "react"; +import { bundledOnlyList, fetchVersionList } from "@paste/lib/versions/registry"; +import type { VersionList } from "@paste/lib/versions/types"; + +export interface VersionListState { + list: VersionList; + loading: boolean; + error: string | null; + retry: () => void; +} + +interface FetchState { + list: VersionList; + loading: boolean; + error: string | null; +} + +export function useVersionList(): VersionListState { + const [state, setState] = useState({ + list: bundledOnlyList(), + loading: true, + error: null, + }); + const [attempt, setAttempt] = useState(0); + + useEffect(() => { + let cancelled = false; + fetchVersionList() + .then((list) => { + if (!cancelled) setState({ list, loading: false, error: null }); + }) + .catch((err) => { + if (!cancelled) + setState((prev) => ({ + ...prev, + loading: false, + error: err instanceof Error ? err.message : String(err), + })); + }); + return () => { + cancelled = true; + }; + }, [attempt]); + + const retry = useCallback(() => { + // Event handler, so synchronous state updates are fine here. + setState((prev) => ({ ...prev, loading: true, error: null })); + setAttempt((a) => a + 1); + }, []); + + return { ...state, retry }; +} diff --git a/docs/app/paste/layout.tsx b/docs/app/paste/layout.tsx new file mode 100644 index 000000000..301392678 --- /dev/null +++ b/docs/app/paste/layout.tsx @@ -0,0 +1,14 @@ +import { WebsiteThemeProvider } from "@/components/website-theme-provider"; +import type { Metadata } from "next"; +import type { ReactNode } from "react"; +import "./paste.css"; + +export const metadata: Metadata = { + title: "OpenUI Paste", + description: + "Paste OpenUI Lang code, validate it against any published @openuidev/lang-core version, replay it as a simulated LLM stream, and see what renders.", +}; + +export default function PasteLayout({ children }: { children: ReactNode }) { + return {children}; +} diff --git a/docs/app/paste/lib/editor/hover.ts b/docs/app/paste/lib/editor/hover.ts new file mode 100644 index 000000000..dfe5caef0 --- /dev/null +++ b/docs/app/paste/lib/editor/hover.ts @@ -0,0 +1,88 @@ +import { hoverTooltip } from "@codemirror/view"; +import type { Extension } from "@codemirror/state"; + +interface PropSchema { + type?: string | string[]; + enum?: unknown[]; + items?: PropSchema; + anyOf?: PropSchema[]; + oneOf?: PropSchema[]; + $ref?: string; + description?: string; +} + +interface ComponentDef { + properties?: Record; + required?: string[]; + description?: string; +} + +export type ComponentDefs = Record; + +function typeLabel(prop: PropSchema | undefined): string { + if (!prop) return "any"; + if (prop.$ref) return prop.$ref.split("/").pop() ?? "any"; + if (prop.enum) { + const shown = prop.enum.slice(0, 4).map((v) => JSON.stringify(v)); + if (prop.enum.length > 4) shown.push("…"); + return shown.join(" | "); + } + const variants = prop.anyOf ?? prop.oneOf; + if (variants) return variants.map(typeLabel).join(" | "); + if (prop.type === "array") { + const inner = typeLabel(prop.items); + return inner === "any" ? "any[]" : `${inner}[]`; + } + if (Array.isArray(prop.type)) return prop.type.join(" | "); + return prop.type ?? "any"; +} + +/** + * VSCode-style hover: over a Capitalized component name, show its signature + * (props in positional order, `?` for optional) from the active library's + * JSON schema. `getDefs` is read at hover time so the extension doesn't need + * rebuilding when the library changes. + */ +export function componentHoverTooltip(getDefs: () => ComponentDefs | null): Extension { + return hoverTooltip((view, pos) => { + const line = view.state.doc.lineAt(pos); + let start = pos; + let end = pos; + while (start > line.from && /\w/.test(view.state.doc.sliceString(start - 1, start))) start--; + while (end < line.to && /\w/.test(view.state.doc.sliceString(end, end + 1))) end++; + if (start === end) return null; + + const word = view.state.doc.sliceString(start, end); + if (!/^[A-Z]/.test(word)) return null; + const def = getDefs()?.[word]; + if (!def?.properties) return null; + + const required = new Set(def.required ?? []); + const args = Object.entries(def.properties).map( + ([name, prop]) => `${name}${required.has(name) ? "" : "?"}: ${typeLabel(prop)}`, + ); + + return { + pos: start, + end, + above: true, + create() { + const dom = document.createElement("div"); + dom.className = "cm-signature-tooltip"; + + const sig = document.createElement("div"); + sig.className = "cm-signature-code"; + sig.textContent = `${word}(${args.join(", ")})`; + dom.appendChild(sig); + + if (def.description) { + const doc = document.createElement("div"); + doc.className = "cm-signature-doc"; + doc.textContent = def.description; + dom.appendChild(doc); + } + return { dom }; + }, + }; + }); +} diff --git a/docs/app/paste/lib/editor/openui-lang.ts b/docs/app/paste/lib/editor/openui-lang.ts new file mode 100644 index 000000000..d5b310e92 --- /dev/null +++ b/docs/app/paste/lib/editor/openui-lang.ts @@ -0,0 +1,22 @@ +import { StreamLanguage } from "@codemirror/language"; + +/** + * Minimal CodeMirror 6 highlighter for OpenUI Lang: + * statements are `name = Component(args)`; "strings"; $stateVars; + * Capitalized names are components. (The language has no comment syntax.) + */ +export const openuiLang = StreamLanguage.define({ + name: "openui-lang", + token(stream) { + if (stream.match(/^"(?:[^"\\]|\\.)*"?/)) return "string"; + if (stream.match(/^\$[A-Za-z_][\w]*/)) return "variableName.special"; + if (stream.match(/^-?\d+(?:\.\d+)?/)) return "number"; + if (stream.match(/^(?:true|false|null)\b/)) return "atom"; + if (stream.match(/^[A-Z][\w]*/)) return "typeName"; + if (stream.match(/^[a-z_][\w]*/)) return "variableName"; + if (stream.match(/^=/)) return "operator"; + if (stream.match(/^[[\]{}(),:]/)) return "punctuation"; + stream.next(); + return null; + }, +}); diff --git a/docs/app/paste/lib/examples.ts b/docs/app/paste/lib/examples.ts new file mode 100644 index 000000000..6dfcfaaab --- /dev/null +++ b/docs/app/paste/lib/examples.ts @@ -0,0 +1,76 @@ +export interface Example { + id: string; + label: string; + code: string; +} + +export const EXAMPLES: Example[] = [ + { + id: "valid", + label: "Valid: basic stack", + code: `root = Card([title, body]) + +title = TextContent("Quarterly Overview") +body = TextContent("Revenue up 12% QoQ; headcount at 128.") +`, + }, + { + id: "invalid", + label: "Validation errors", + code: `root = Stack([heading, Table(5, []), missingRef]) + +heading = TextContent("Broken example") +stray = TextContent("nobody references me") +`, + }, + { + id: "partial", + label: "Truncated / incomplete", + code: `root = Stack([title, body, chart]) + +title = TextContent("Live streaming demo") +body = TextContent("This program is truncated mid-statement to show partial parsing.") +chart = BarChart(["Q1", "Q2", "Q3"], [series`, + }, + { + id: "missing-required", + label: "Missing required props", + code: `root = Stack([title, code, chart, pic]) + +title = TextContent() +code = CodeBlock("typescript") +chart = BarChart(["Q1", "Q2", "Q3"]) +pic = Image() +`, + }, + { + id: "kitchen-sink", + label: "Kitchen sink (form + table + chart)", + code: `root = Stack([title, note, form, tbl, chart]) + +title = TextContent("paste playground", "large-heavy") +note = Callout("info", "Demo", "A form with validation, a table and a chart — rendered fully client-side.") +form = Form("contact", btns, [nameField, emailField]) +nameField = FormControl("Name", Input("name", "Your name", "text", { required: true, minLength: 2 })) +emailField = FormControl("Email", Input("email", "you@example.com", "email", { required: true, email: true })) +btns = Buttons([Button("Submit", Action([@ToAssistant("Submit")]), "primary")]) +tbl = Table([Col("Language", langs), Col("Users (M)", users)]) +langs = ["Python", "JavaScript", "Go"] +users = [15.7, 14.2, 5.2] +chart = BarChart(["Q1", "Q2", "Q3"], [Series("Revenue", [120, 150, 180])], "grouped") +`, + }, + { + id: "mock-tools", + label: "Query() with mock tools", + code: `root = Stack([title, note, tickets]) + +title = TextContent("Ticket dashboard", "large-heavy") +note = TextContent("The Query() below hits the playground's mock tool provider: any tool name resolves with sample data.") +data = Query("list_tickets", {}, {items: []}) +tickets = Table([Col("Ticket", data.items.label), Col("Priority", data.items.value)]) +`, + }, +]; + +export const DEFAULT_EXAMPLE = EXAMPLES[0]; diff --git a/docs/app/paste/lib/groupErrors.ts b/docs/app/paste/lib/groupErrors.ts new file mode 100644 index 000000000..de1e17482 --- /dev/null +++ b/docs/app/paste/lib/groupErrors.ts @@ -0,0 +1,23 @@ +import type { ValidationError } from "./versions/types"; + +/** Known codes, in display order; unknown codes (future versions) sort after. */ +export const KNOWN_ERROR_CODES = [ + "type-mismatch", + "missing-required", + "null-required", + "excess-args", + "unknown-component", + "inline-reserved", +] as const; + +export function groupByCode(errors: ValidationError[]): Map { + const map = new Map(); + for (const code of KNOWN_ERROR_CODES) map.set(code, []); + for (const e of errors) { + const list = map.get(e.code) ?? []; + list.push(e); + map.set(e.code, list); + } + for (const [code, list] of map) if (list.length === 0) map.delete(code); + return map; +} diff --git a/docs/app/paste/lib/libraries.ts b/docs/app/paste/lib/libraries.ts new file mode 100644 index 000000000..ba780ecef --- /dev/null +++ b/docs/app/paste/lib/libraries.ts @@ -0,0 +1,41 @@ +import type { Library } from "@openuidev/react-lang"; +import { openuiChatLibrary, openuiLibrary } from "@openuidev/react-ui/genui-lib"; +import { chatLibrary } from "@openuidev/thesys"; + +export type LibraryId = "openui" | "chat" | "thesys"; + +export interface LibraryEntry { + id: LibraryId; + label: string; + library: Library; +} + +export const LIBRARIES: LibraryEntry[] = [ + { id: "openui", label: "openui", library: openuiLibrary as Library }, + { id: "chat", label: "openui chat", library: openuiChatLibrary as Library }, + { id: "thesys", label: "openui thesys", library: chatLibrary as Library }, +]; + +export function getLibrary(id: LibraryId): LibraryEntry { + return LIBRARIES.find((l) => l.id === id) ?? LIBRARIES[0]; +} + +// toJSONSchema() walks every component's zod schema — do it once per library. +const schemaCache = new Map(); + +export function getSchema(id: LibraryId): unknown { + let schema = schemaCache.get(id); + if (!schema) { + schema = getLibrary(id).library.toJSONSchema(); + schemaCache.set(id, schema); + } + return schema; +} + +export function getRootName(id: LibraryId): string | undefined { + return getLibrary(id).library.root; +} + +export function getComponentNames(id: LibraryId): string[] { + return Object.keys(getLibrary(id).library.components); +} diff --git a/docs/app/paste/lib/mockTools.ts b/docs/app/paste/lib/mockTools.ts new file mode 100644 index 000000000..f37cd7427 --- /dev/null +++ b/docs/app/paste/lib/mockTools.ts @@ -0,0 +1,43 @@ +/** + * Mock tool provider for the render window: any Query()/Mutation() tool name + * resolves after a short delay with canned data, so interactive snippets + * render instead of erroring on unregistered tools. + */ +const CANNED_RESULT = { + status: "ok", + note: "Mock response from the paste playground — tools are not connected.", + items: [ + { id: 1, label: "Alpha", value: 42 }, + { id: 2, label: "Beta", value: 17 }, + { id: 3, label: "Gamma", value: 8 }, + ], +}; + +export interface MockToolCall { + tool: string; + args: Record; + at: number; +} + +// react-lang treats anything with a callTool() method as an MCP client, and +// the runtime may probe `then` (thenable checks) — the catch-all must not +// answer to those, or the provider is misdetected. +const RESERVED = new Set(["callTool", "then", "toJSON", "constructor"]); + +/** + * `onCall` fires for every mocked invocation so the UI can show users that + * their Query()/Mutation() calls hit a mock, not a real backend. + */ +export function createMockToolProvider(onCall?: (call: MockToolCall) => void) { + return new Proxy({} as Record) => Promise>, { + get: (_target, prop) => { + if (typeof prop !== "string" || RESERVED.has(prop)) return undefined; + return async (args: Record) => { + onCall?.({ tool: prop, args, at: Date.now() }); + await new Promise((r) => setTimeout(r, 400)); + return { ...CANNED_RESULT, tool: prop, receivedArgs: args }; + }; + }, + has: (_target, prop) => typeof prop === "string" && !RESERVED.has(prop), + }); +} diff --git a/docs/app/paste/lib/parse.ts b/docs/app/paste/lib/parse.ts new file mode 100644 index 000000000..58a0c8990 --- /dev/null +++ b/docs/app/paste/lib/parse.ts @@ -0,0 +1,83 @@ +import type { + LoadedLangCore, + OpenUIError, + ParseResult, + ValidationError, +} from "./versions/types"; + +export interface ValidationOutcome { + result: ParseResult | null; + enriched: OpenUIError[] | null; + /** The parser itself threw — happens on some old lang-core versions. */ + fatal: string | null; +} + +export const EMPTY_OUTCOME: ValidationOutcome = { result: null, enriched: null, fatal: null }; + +/** + * Old lang-core versions return ParseResults with missing fields (0.1.x has + * no meta.unresolved/orphaned, for example) — panels would crash on `.map`. + * Coerce every result to the full modern shape before it reaches React. + */ +export function normalizeResult(raw: unknown): ParseResult | null { + if (!raw || typeof raw !== "object") return null; + const r = raw as Partial; + const meta = (r.meta ?? {}) as Partial; + return { + root: r.root ?? null, + meta: { + incomplete: !!meta.incomplete, + unresolved: Array.isArray(meta.unresolved) ? meta.unresolved : [], + orphaned: Array.isArray(meta.orphaned) ? meta.orphaned : [], + statementCount: typeof meta.statementCount === "number" ? meta.statementCount : 0, + errors: Array.isArray(meta.errors) ? meta.errors : [], + }, + stateDeclarations: r.stateDeclarations, + queryStatements: r.queryStatements, + mutationStatements: r.mutationStatements, + }; +} + +/** + * One-shot validation with the user-selected lang-core version. Everything a + * dynamically loaded module does is wrapped — a throw becomes `fatal`, never + * an unhandled exception. (If parsing ever needs to move off the main thread, + * this function is the seam to put behind a worker.) + */ +export function runValidation( + code: string, + loaded: LoadedLangCore, + schema: unknown, + rootName: string | undefined, + componentNames: string[], +): ValidationOutcome { + if (!code.trim()) return EMPTY_OUTCOME; + let result: ParseResult | null; + try { + result = normalizeResult(loaded.mod.createParser!(schema, rootName).parse(code)); + } catch (err) { + return { + result: null, + enriched: null, + fatal: err instanceof Error ? err.message : String(err), + }; + } + if (!result) { + return { result: null, enriched: null, fatal: "Parser returned an unrecognizable result." }; + } + return { result, enriched: tryEnrich(result.meta.errors, loaded, schema, componentNames), fatal: null }; +} + +export function tryEnrich( + errors: ValidationError[], + loaded: LoadedLangCore, + schema: unknown, + componentNames: string[], +): OpenUIError[] | null { + if (!loaded.capabilities.enrich || errors.length === 0) return null; + try { + return loaded.mod.enrichErrors!(errors, schema, componentNames); + } catch { + return null; + } +} diff --git a/docs/app/paste/lib/streaming/chunker.ts b/docs/app/paste/lib/streaming/chunker.ts new file mode 100644 index 000000000..0f9b837b7 --- /dev/null +++ b/docs/app/paste/lib/streaming/chunker.ts @@ -0,0 +1,54 @@ +export interface StreamChunk { + text: string; + /** Wait before pushing this chunk — models inconsistent LLM/network arrival. */ + delayMs: number; +} + +export type ChunkStrategy = "24" | "line" | "char" | "llm"; + +export const CHUNK_STRATEGIES: { id: ChunkStrategy; label: string }[] = [ + { id: "llm", label: "LLM-like" }, + { id: "24", label: "24 chars" }, + { id: "line", label: "Per line" }, + { id: "char", label: "Per char" }, +]; + +/** Deterministic PRNG so llm-strategy runs are reproducible via seed. */ +export function mulberry32(seed: number): () => number { + let a = seed >>> 0; + return () => { + a |= 0; + a = (a + 0x6d2b79f5) | 0; + let t = Math.imul(a ^ (a >>> 15), 1 | a); + t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t; + return ((t ^ (t >>> 14)) >>> 0) / 4294967296; + }; +} + +export function splitChunks(source: string, spec: ChunkStrategy, rng: () => number): StreamChunk[] { + if (spec === "llm") { + const chunks: StreamChunk[] = []; + let i = 0; + while (i < source.length) { + const len = rng() < 0.8 ? 1 + Math.floor(rng() * 12) : 13 + Math.floor(rng() * 48); + const text = source.slice(i, i + len); + i += text.length; + const r = rng(); + const delayMs = + r < 0.15 ? 0 : r < 0.9 ? 5 + Math.floor(rng() * 75) : 200 + Math.floor(rng() * 400); + chunks.push({ text, delayMs }); + } + return chunks; + } + if (spec === "line") { + return source + .split(/(?<=\n)/) + .filter((s) => s.length > 0) + .map((text) => ({ text, delayMs: 0 })); + } + const n = spec === "char" ? 1 : Math.max(1, parseInt(spec, 10) || 24); + const chunks: StreamChunk[] = []; + for (let i = 0; i < source.length; i += n) + chunks.push({ text: source.slice(i, i + n), delayMs: 0 }); + return chunks; +} diff --git a/docs/app/paste/lib/streaming/usePlayback.ts b/docs/app/paste/lib/streaming/usePlayback.ts new file mode 100644 index 000000000..1f1597d38 --- /dev/null +++ b/docs/app/paste/lib/streaming/usePlayback.ts @@ -0,0 +1,273 @@ +"use client"; + +import { useCallback, useRef, useState } from "react"; +import { normalizeResult } from "../parse"; +import type { LoadedLangCore, ParseResult, StreamParserLike } from "../versions/types"; +import { type ChunkStrategy, type StreamChunk, mulberry32, splitChunks } from "./chunker"; + +export interface TraceRow { + i: number; + chunkPreview: string; + delayMs: number; + rootPresent: boolean; + rootAppeared: boolean; + rootDropped: boolean; + statementCount: number; + incomplete: boolean; + unresolvedCount: number; + errorCount: number; +} + +export type PlaybackStatus = "idle" | "playing" | "paused" | "done"; + +export interface PlaybackState { + status: PlaybackStatus; + /** Accumulated source text pushed so far — feed this to the Renderer. */ + prefix: string; + chunkIndex: number; + totalChunks: number; + trace: TraceRow[]; + traceTruncated: boolean; + result: ParseResult | null; + /** Streaming result vs one-shot parse of the full source, checked on finish. */ + convergence: "converged" | "diverged" | null; + /** True when the selected version has no createStreamingParser (re-parse per chunk). */ + emulated: boolean; + fatal: string | null; +} + +const IDLE: PlaybackState = { + status: "idle", + prefix: "", + chunkIndex: 0, + totalChunks: 0, + trace: [], + traceTruncated: false, + result: null, + convergence: null, + emulated: false, + fatal: null, +}; + +const TRACE_CAP = 5000; +const BASE_INTERVAL_MS = 40; + +interface Session { + /** Snapshot of the editor content when playback started. */ + source: string; + chunks: StreamChunk[]; + strategy: ChunkStrategy; + emulated: boolean; + push: (chunk: string, prefix: string) => ParseResult; + getFinal: () => ParseResult; + prefix: string; + index: number; + lastRootPresent: boolean; + rows: TraceRow[]; + truncated: boolean; +} + +export interface PlaybackControls { + state: PlaybackState; + start: (opts: { strategy: ChunkStrategy; seed: number }) => void; + pause: () => void; + resume: () => void; + step: () => void; + reset: () => void; + setSpeed: (speed: number) => void; + speed: number; +} + +export function usePlayback( + code: string, + loaded: LoadedLangCore | null, + schema: unknown, + rootName: string | undefined, +): PlaybackControls { + const [state, setState] = useState(IDLE); + const [speed, setSpeedState] = useState(1); + const speedRef = useRef(1); + // Incremented on pause/reset/start — in-flight timeout callbacks bail when stale. + const generation = useRef(0); + const session = useRef(null); + + const setSpeed = useCallback((s: number) => { + speedRef.current = s; + setSpeedState(s); + }, []); + + const finish = useCallback( + (s: Session, l: LoadedLangCore) => { + let convergence: PlaybackState["convergence"] = null; + let fatal: string | null = null; + let result: ParseResult | null = null; + try { + result = normalizeResult(s.getFinal()); + const oneShot = normalizeResult(l.mod.createParser!(schema, rootName).parse(s.source)); + convergence = + JSON.stringify(result?.root) === JSON.stringify(oneShot?.root) + ? "converged" + : "diverged"; + } catch (err) { + fatal = err instanceof Error ? err.message : String(err); + } + setState((prev) => ({ + ...prev, + status: "done", + result: result ?? prev.result, + convergence, + fatal, + })); + }, + [schema, rootName], + ); + + /** Parse one chunk, record a trace row, publish state. Returns false on fatal error. */ + const advance = useCallback( + (s: Session, status: PlaybackStatus): boolean => { + const chunk = s.chunks[s.index]; + s.prefix += chunk.text; + let result: ParseResult | null = null; + let fatal: string | null = null; + try { + result = normalizeResult(s.push(chunk.text, s.prefix)); + } catch (err) { + fatal = err instanceof Error ? err.message : String(err); + } + const rootPresent = !!result?.root; + if (result) { + if (s.rows.length >= TRACE_CAP) { + s.truncated = true; + } else { + s.rows.push({ + i: s.index, + chunkPreview: chunk.text.length > 40 ? `${chunk.text.slice(0, 40)}…` : chunk.text, + delayMs: chunk.delayMs, + rootPresent, + rootAppeared: rootPresent && !s.lastRootPresent, + rootDropped: !rootPresent && s.lastRootPresent, + statementCount: result.meta.statementCount, + incomplete: result.meta.incomplete, + unresolvedCount: result.meta.unresolved.length, + errorCount: result.meta.errors.length, + }); + } + } + s.lastRootPresent = rootPresent; + s.index += 1; + setState({ + status: fatal ? "done" : status, + prefix: s.prefix, + chunkIndex: s.index, + totalChunks: s.chunks.length, + trace: [...s.rows], + traceTruncated: s.truncated, + result, + convergence: null, + emulated: s.emulated, + fatal, + }); + return !fatal; + }, + [], + ); + + const play = useCallback( + (s: Session, l: LoadedLangCore) => { + const gen = generation.current; + const tick = () => { + if (generation.current !== gen || session.current !== s) return; + if (s.index >= s.chunks.length) { + finish(s, l); + return; + } + if (!advance(s, "playing")) return; + if (s.index >= s.chunks.length) { + finish(s, l); + return; + } + schedule(); + }; + const schedule = () => { + const next = s.chunks[s.index]; + const base = s.strategy === "llm" && next ? next.delayMs : BASE_INTERVAL_MS; + setTimeout(tick, Math.max(0, base / speedRef.current)); + }; + schedule(); + }, + [advance, finish], + ); + + const start = useCallback( + ({ strategy, seed }: { strategy: ChunkStrategy; seed: number }) => { + if (!loaded?.compatible || !loaded.mod.createParser || !code.trim()) return; + generation.current += 1; + + const emulated = !loaded.capabilities.streaming; + let push: Session["push"]; + let getFinal: Session["getFinal"]; + if (emulated) { + // O(n²) full re-parse per chunk — fine at playground scale. + let last: ParseResult | null = null; + push = (_chunk, prefix) => { + last = loaded.mod.createParser!(schema, rootName).parse(prefix); + return last; + }; + getFinal = () => last ?? loaded.mod.createParser!(schema, rootName).parse(code); + } else { + const sp: StreamParserLike = loaded.mod.createStreamingParser!(schema, rootName); + push = (chunk) => sp.push(chunk); + getFinal = () => sp.getResult(); + } + + const s: Session = { + source: code, + chunks: splitChunks(code, strategy, mulberry32(seed)), + strategy, + emulated, + push, + getFinal, + prefix: "", + index: 0, + lastRootPresent: false, + rows: [], + truncated: false, + }; + session.current = s; + setState({ ...IDLE, status: "playing", totalChunks: s.chunks.length, emulated }); + play(s, loaded); + }, + [code, loaded, schema, rootName, play], + ); + + const pause = useCallback(() => { + generation.current += 1; + setState((prev) => (prev.status === "playing" ? { ...prev, status: "paused" } : prev)); + }, []); + + const resume = useCallback(() => { + const s = session.current; + if (!s || !loaded) return; + generation.current += 1; + setState((prev) => (prev.status === "paused" ? { ...prev, status: "playing" } : prev)); + play(s, loaded); + }, [loaded, play]); + + const step = useCallback(() => { + const s = session.current; + if (!s || !loaded) return; + if (s.index >= s.chunks.length) { + finish(s, loaded); + return; + } + if (advance(s, "paused") && s.index >= s.chunks.length) finish(s, loaded); + }, [advance, finish, loaded]); + + const reset = useCallback(() => { + generation.current += 1; + session.current = null; + setState(IDLE); + }, []); + + return { state, start, pause, resume, step, reset, setSpeed, speed }; +} diff --git a/docs/app/paste/lib/versions/loader.ts b/docs/app/paste/lib/versions/loader.ts new file mode 100644 index 000000000..22cb58791 --- /dev/null +++ b/docs/app/paste/lib/versions/loader.ts @@ -0,0 +1,132 @@ +import * as bundled from "@openuidev/lang-core"; +import type { LangCoreModule, LoadedLangCore, ParseResult } from "./types"; + +export const BUNDLED_LANG_CORE_VERSION = "0.2.9"; + +// Both webpack and Turbopack rewrite `import(expr)`; magic comments differ +// between them and drift across Next versions. `new Function` is the one +// bundler-proof way to get a native dynamic import. +const dynamicImport = new Function("u", "return import(u)") as ( + u: string, +) => Promise>; + +const CDN_SOURCES = [ + { + source: "esm.sh" as const, + url: (v: string) => `https://esm.sh/@openuidev/lang-core@${v}`, + }, + { + source: "jsdelivr" as const, + url: (v: string) => `https://cdn.jsdelivr.net/npm/@openuidev/lang-core@${v}/+esm`, + }, +]; + +const cache = new Map>(); + +function detect( + mod: LangCoreModule, + version: string, + source: LoadedLangCore["source"], +): LoadedLangCore { + if (typeof mod.createParser !== "function") { + return { + version, + mod, + source, + compatible: false, + reason: "This version does not export createParser().", + capabilities: { streaming: false, streamSet: false, enrich: false }, + }; + } + + // Smoke test: the ParseResult shape must be recognizable or every panel breaks. + try { + const result: ParseResult = mod + .createParser({ $defs: { Text: { type: "object", properties: {} } } }) + .parse('root = Text("hi")'); + if ( + !result || + typeof result !== "object" || + !result.meta || + !Array.isArray(result.meta.errors) + ) { + throw new Error("ParseResult has no meta.errors array"); + } + } catch (err) { + return { + version, + mod, + source, + compatible: false, + reason: `Smoke test failed: ${err instanceof Error ? err.message : String(err)}`, + capabilities: { streaming: false, streamSet: false, enrich: false }, + }; + } + + let streamSet = false; + const streaming = typeof mod.createStreamingParser === "function"; + if (streaming) { + try { + const sp = mod.createStreamingParser!({ $defs: {} }); + streamSet = typeof sp.set === "function"; + } catch { + // constructor threw — treat streaming as unsupported + return { + version, + mod, + source, + compatible: true, + capabilities: { + streaming: false, + streamSet: false, + enrich: typeof mod.enrichErrors === "function", + }, + }; + } + } + + return { + version, + mod, + source, + compatible: true, + capabilities: { streaming, streamSet, enrich: typeof mod.enrichErrors === "function" }, + }; +} + +async function loadUncached(version: string): Promise { + if (version === BUNDLED_LANG_CORE_VERSION) { + return detect(bundled as LangCoreModule, version, "bundled"); + } + for (const cdn of CDN_SOURCES) { + try { + const mod = await Promise.race([ + dynamicImport(cdn.url(version)), + new Promise((_, reject) => + // esm.sh can hang while building a never-requested version, not just 404. + setTimeout(() => reject(new Error("CDN timeout")), 8000), + ), + ]); + return detect(mod as LangCoreModule, version, cdn.source); + } catch { + continue; + } + } + // All CDNs unreachable — validate with the bundled parser and say so in the UI. + return detect(bundled as LangCoreModule, BUNDLED_LANG_CORE_VERSION, "bundled"); +} + +/** + * Load a specific published version. Resolved modules (and in-flight loads) + * are cached per version so switching back is instant. + */ +export function loadLangCore(version: string): Promise { + let pending = cache.get(version); + if (!pending) { + pending = loadUncached(version); + cache.set(version, pending); + // Don't poison the cache with transient failures (e.g. offline). + pending.catch(() => cache.delete(version)); + } + return pending; +} diff --git a/docs/app/paste/lib/versions/registry.ts b/docs/app/paste/lib/versions/registry.ts new file mode 100644 index 000000000..10dc9ee36 --- /dev/null +++ b/docs/app/paste/lib/versions/registry.ts @@ -0,0 +1,76 @@ +import { rcompare, valid } from "semver"; +import type { VersionList } from "./types"; +import { BUNDLED_LANG_CORE_VERSION } from "./loader"; + +const REGISTRY_URL = "https://registry.npmjs.org/@openuidev/lang-core"; +const CACHE_KEY = "paste:lang-core-versions:v1"; +const CACHE_TTL_MS = 10 * 60 * 1000; + +interface CachedList { + fetchedAt: number; + versions: string[]; + latest: string | null; +} + +function readCache(): CachedList | null { + if (typeof window === "undefined") return null; + try { + const raw = sessionStorage.getItem(CACHE_KEY); + if (!raw) return null; + const parsed = JSON.parse(raw) as CachedList; + if (Date.now() - parsed.fetchedAt > CACHE_TTL_MS) return null; + return parsed; + } catch { + return null; + } +} + +function writeCache(entry: CachedList): void { + try { + sessionStorage.setItem(CACHE_KEY, JSON.stringify(entry)); + } catch { + // storage full / private mode — cache is best-effort + } +} + +function group(versions: string[], latest: string | null): VersionList { + const sorted = [...versions].filter((v) => valid(v)).sort(rcompare); + const groups = new Map(); + for (const v of sorted) { + const [major, minor] = v.split("."); + const label = `${major}.${minor}`; + const list = groups.get(label) ?? []; + list.push(v); + groups.set(label, list); + } + return { + groups: [...groups.entries()].map(([label, vs]) => ({ label, versions: vs })), + latest, + }; +} + +/** Fallback entry so the picker always works even when the registry is down. */ +export function bundledOnlyList(): VersionList { + return group([BUNDLED_LANG_CORE_VERSION], BUNDLED_LANG_CORE_VERSION); +} + +export async function fetchVersionList(): Promise { + const cached = readCache(); + if (cached) return group(cached.versions, cached.latest); + + const res = await fetch(REGISTRY_URL, { + // Corgi doc: much smaller than the full packument, still has versions + dist-tags. + headers: { Accept: "application/vnd.npm.install-v1+json" }, + signal: AbortSignal.timeout(10_000), + }); + if (!res.ok) throw new Error(`npm registry responded ${res.status}`); + const doc = (await res.json()) as { + versions?: Record; + "dist-tags"?: Record; + }; + const versions = Object.keys(doc.versions ?? {}); + const latest = doc["dist-tags"]?.latest ?? null; + if (versions.length === 0) throw new Error("npm registry returned no versions"); + writeCache({ fetchedAt: Date.now(), versions, latest }); + return group(versions, latest); +} diff --git a/docs/app/paste/lib/versions/types.ts b/docs/app/paste/lib/versions/types.ts new file mode 100644 index 000000000..094f3c9d6 --- /dev/null +++ b/docs/app/paste/lib/versions/types.ts @@ -0,0 +1,96 @@ +/** + * Structural types for whatever version of @openuidev/lang-core the user + * selects. Never import types from the package itself here — these must + * compile against ANY published version, old or new (same approach as + * lang-harness/src/langcore.ts). + */ + +export interface ValidationError { + code: string; + component: string; + path: string; + message: string; + statementId?: string; +} + +export interface OpenUIError { + source: string; + code: string; + message: string; + component?: string; + path?: string; + statementId?: string; + hint?: string; +} + +export interface ElementNode { + type: "element"; + typeName: string; + props: Record; + partial: boolean; + statementId?: string; + hasDynamicProps?: boolean; +} + +export interface ParseMeta { + incomplete: boolean; + unresolved: string[]; + orphaned: string[]; + statementCount: number; + errors: ValidationError[]; +} + +export interface ParseResult { + root: ElementNode | null; + meta: ParseMeta; + stateDeclarations?: Record; + queryStatements?: unknown[]; + mutationStatements?: unknown[]; +} + +export interface ParserLike { + parse(src: string): ParseResult; +} + +export interface StreamParserLike { + push(chunk: string): ParseResult; + /** Diffs against internal buffer; resets if text was replaced. Newer versions only. */ + set?(fullText: string): ParseResult; + getResult(): ParseResult; +} + +export interface LangCoreModule { + createParser?: (schema: unknown, rootName?: string) => ParserLike; + createStreamingParser?: (schema: unknown, rootName?: string) => StreamParserLike; + enrichErrors?: ( + errors: ValidationError[], + schema: unknown, + componentNames: string[], + ) => OpenUIError[]; +} + +export interface LoadedLangCore { + version: string; + mod: LangCoreModule; + /** Where the module actually came from. */ + source: "esm.sh" | "jsdelivr" | "bundled"; + compatible: boolean; + /** Human-readable reason when compatible === false. */ + reason?: string; + capabilities: { + streaming: boolean; + streamSet: boolean; + enrich: boolean; + }; +} + +export interface VersionGroup { + /** "0.2", "1.0", ... */ + label: string; + versions: string[]; +} + +export interface VersionList { + groups: VersionGroup[]; + latest: string | null; +} diff --git a/docs/app/paste/page.tsx b/docs/app/paste/page.tsx new file mode 100644 index 000000000..54a9c6e45 --- /dev/null +++ b/docs/app/paste/page.tsx @@ -0,0 +1,14 @@ +"use client"; + +import dynamic from "next/dynamic"; + +// Client-only: the playground drives CodeMirror, CDN version loading and +// playback in the browser, and @openuidev/thesys does not survive SSR. +const Playground = dynamic(() => import("./components/Playground").then((m) => m.Playground), { + ssr: false, + loading: () =>
Loading playground…
, +}); + +export default function PastePage() { + return ; +} diff --git a/docs/app/paste/paste.css b/docs/app/paste/paste.css new file mode 100644 index 000000000..badfb4163 --- /dev/null +++ b/docs/app/paste/paste.css @@ -0,0 +1,870 @@ +/* paste playground — ported from the standalone app. Every rule is scoped + under .playground (same convention as app/playground/layout.css) so nothing + leaks into other docs routes after navigation. Colors/spacing come from the + --openui-* token sheet already loaded by app/global.css. */ + +:where(.playground, .playground *, .playground *::before, .playground *::after) { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +.playground { + --paste-border: 1px solid var(--openui-border-default, rgba(128, 128, 128, 0.25)); + --paste-mono: var(--openui-font-code, ui-monospace, SFMono-Regular, Menlo, monospace); + height: 100vh; + display: flex; + flex-direction: column; + background: var(--openui-background, #fff); + color: var(--openui-text-neutral-primary, #171717); + font-size: 16px; +} + +/* ── toolbar ───────────────────────────────────────────────────────────── */ + +.playground .toolbar { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--openui-space-m, 16px); + padding: var(--openui-space-m, 16px); + border-bottom: var(--paste-border); + flex-wrap: wrap; +} + +.playground .toolbar-brand { + display: flex; + align-items: baseline; + gap: var(--openui-space-s, 10px); +} + +.playground .toolbar-logo { + font-weight: 600; + font-size: var(--openui-font-size-xl, 20px); +} + +.playground .toolbar-tagline { + color: var(--openui-text-neutral-secondary, #666); + font-size: var(--openui-font-size-xl, 14px); +} + +.playground .toolbar-controls { + display: flex; + align-items: center; + gap: var(--openui-space-m, 16px); + flex-wrap: wrap; +} + +.playground .toolbar-field { + display: flex; + align-items: center; + gap: var(--openui-space-2xs, 6px); +} + +.playground .toolbar-label { + color: var(--openui-text-neutral-secondary, #666); + font-size: var(--openui-font-size-xs, 12px); +} + +.playground .toolbar-divider { + width: 1px; + height: 22px; + background: var(--openui-border-default, rgba(128, 128, 128, 0.25)); +} + +/* ── help dialog ───────────────────────────────────────────────────────── */ + +.playground .help-overlay { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.4); + display: flex; + align-items: center; + justify-content: center; + z-index: 200; + padding: var(--openui-space-l, 24px); +} + +.playground .help-dialog { + background: var(--openui-background, #fff); + border: var(--paste-border); + border-radius: var(--openui-radius-l, 12px); + box-shadow: var(--openui-shadow-2xl, 0 24px 64px rgba(0, 0, 0, 0.25)); + max-width: 640px; + width: 100%; + max-height: 80vh; + display: flex; + flex-direction: column; + overflow: hidden; +} + +.playground .help-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: var(--openui-space-m, 16px) var(--openui-space-l, 24px); + border-bottom: var(--paste-border); +} + +.playground .help-header h2 { + font-size: var(--openui-font-size-lg, 18px); + font-weight: 700; +} + +.playground .help-body { + padding: var(--openui-space-l, 24px); + overflow-y: auto; + display: flex; + flex-direction: column; + gap: var(--openui-space-s, 10px); + font-size: var(--openui-font-size-m, 14px); + line-height: 1.55; +} + +.playground .help-body h3 { + font-size: var(--openui-font-size-m, 14px); + font-weight: 700; + margin-top: var(--openui-space-s, 10px); +} + +.playground .help-body ol, +.playground .help-body ul { + padding-left: var(--openui-space-l, 24px); + display: flex; + flex-direction: column; + gap: var(--openui-space-2xs, 6px); +} + +.playground .help-body code { + font-family: var(--paste-mono); + font-size: 0.9em; + background: var(--openui-sunk, rgba(0, 0, 0, 0.05)); + border-radius: var(--openui-radius-xs, 4px); + padding: 1px var(--openui-space-2xs, 6px); +} + +/* ── stream controls ───────────────────────────────────────────────────── */ + +.playground .stream-controls { + display: flex; + align-items: center; + gap: var(--openui-space-s, 10px); + flex-wrap: wrap; +} + +.playground .seed-input { + width: 70px; +} + +.playground .stream-progress { + color: var(--openui-text-neutral-secondary, #666); + font-size: var(--openui-font-size-xs, 12px); + font-family: var(--paste-mono); +} + +/* ── split layout ──────────────────────────────────────────────────────── */ + +.playground .main-split { + flex: 1; + min-height: 0; +} + +.playground .resize-handle { + width: 5px; + background: transparent; + border-left: var(--paste-border); + transition: background 0.15s; +} + +.playground .resize-handle:hover, +.playground .resize-handle:active { + background: var(--openui-interactive-accent-default, #4467f6); +} + +/* react-resizable-panels v4 applies Panel className to a nested div */ +.playground .split-panel { + height: 100%; + min-width: 0; +} + +.playground .editor-pane { + height: 100%; + position: relative; + overflow: hidden; +} + +.playground .editor-pane .cm-editor { + height: 100%; +} + +.playground .editor-lock { + position: absolute; + bottom: var(--openui-space-s, 10px); + right: var(--openui-space-s, 10px); + background: var(--openui-elevated, #f5f5f5); + border: var(--paste-border); + border-radius: var(--openui-radius-full, 999px); + padding: var(--openui-space-3xs, 2px) var(--openui-space-s, 10px); + font-size: var(--openui-font-size-2xs, 10px); + color: var(--openui-text-neutral-secondary, #666); +} + +/* ── output tabs ───────────────────────────────────────────────────────── */ + +.playground .output-pane { + height: 100%; + display: flex; + flex-direction: column; + min-width: 0; +} + +.playground .output-tabs { + display: flex; + flex-direction: column; + height: 100%; + min-height: 0; +} + +.playground .tab-strip { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--openui-space-s, 10px); + padding: var(--openui-space-2xs, 6px) var(--openui-space-s, 10px); + border-bottom: var(--paste-border); +} + +/* Our .tab-strip border spans the full pane width — drop the library's own + list underline so there aren't two stacked horizontal rules. */ +.playground .tab-strip .openui-tabs-list-container::after { + display: none; +} + +.playground .banner-slot { + padding: var(--openui-space-xs, 8px) var(--openui-space-m, 16px) 0; +} + +.playground .tab-body { + flex: 1; + min-height: 0; + overflow: hidden; + display: flex; + flex-direction: column; +} + +/* Radix keeps inactive TabsContent in the DOM with [hidden]; display rules + would otherwise override it and stack every pane below the active one. */ +.playground .tab-body[hidden], +.playground .tab-body[data-state="inactive"] { + display: none; +} + +/* The library's TabsContent wraps children in .openui-tabs-content-inner, + which is height-unconstrained — without this, .panel-scroll grows to its + content and nothing ever scrolls. */ +.playground .tab-body > .openui-tabs-content-inner { + display: flex; + flex-direction: column; + flex: 1; + min-height: 0; + overflow: hidden; +} + +.playground .panel-scroll { + overflow: auto; + padding: var(--openui-space-m, 16px); + flex: 1; +} + +.playground .panel-empty { + padding: var(--openui-space-xl, 32px); + color: var(--openui-text-neutral-tertiary, #999); + text-align: center; + margin: auto; +} + +/* ── validation panel ──────────────────────────────────────────────────── */ + +.playground .meta-strip { + display: flex; + gap: var(--openui-space-2xs, 6px); + flex-wrap: wrap; + margin-bottom: var(--openui-space-m, 16px); +} + +.playground .all-clear { + color: var(--openui-text-success-primary, #171); + font-weight: 500; + display: flex; + align-items: center; + gap: var(--openui-space-2xs, 6px); +} + +.playground .error-group { + margin-bottom: var(--openui-space-l, 24px); +} + +.playground .error-group h3 { + font-size: var(--openui-font-size-s, 13px); + margin-bottom: var(--openui-space-xs, 8px); +} + +.playground .error-group .count { + color: var(--openui-text-neutral-tertiary, #999); + font-weight: 400; +} + +.playground .error-group ul { + list-style: none; +} + +.playground .error-row { + padding: var(--openui-space-xs, 8px); + border: var(--paste-border); + border-radius: var(--openui-radius-s, 6px); + margin-bottom: var(--openui-space-xs, 8px); +} + +.playground .err-head { + display: flex; + align-items: center; + gap: var(--openui-space-xs, 8px); +} + +.playground .err-component { + font-weight: 600; + font-family: var(--paste-mono); +} + +.playground .err-path { + color: var(--openui-text-neutral-secondary, #666); + font-family: var(--paste-mono); +} + +.playground .err-message { + margin-top: var(--openui-space-3xs, 2px); +} + +.playground .err-hint { + margin-top: var(--openui-space-3xs, 2px); + color: var(--openui-text-info-primary, #147); + font-size: var(--openui-font-size-s, 13px); + display: flex; + align-items: center; + gap: var(--openui-space-2xs, 6px); +} + +.playground .fatal-card { + border: 1px solid var(--openui-text-danger-primary, #a11); + border-radius: var(--openui-radius-m, 8px); + padding: var(--openui-space-m, 16px); + color: var(--openui-text-danger-primary, #a11); +} + +.playground .fatal-card pre { + margin: var(--openui-space-xs, 8px) 0; + font-family: var(--paste-mono); + white-space: pre-wrap; +} + +/* ── render panel ──────────────────────────────────────────────────────── */ + +.playground .render-panel { + display: flex; + flex-direction: column; + flex: 1; + min-height: 0; + overflow: auto; +} + +.playground .render-seam { + padding: var(--openui-space-2xs, 6px) var(--openui-space-m, 16px); + color: var(--openui-text-neutral, #999); + font-size: var(--openui-font-size-xs, 10px); + margin-bottom: var(--openui-space-xs); +} + +.playground .render-canvas { + padding: var(--openui-space-l, 24px); + flex: 1; +} + +.playground .render-details { + border-top: var(--paste-border); + padding: var(--openui-space-xs, 8px) var(--openui-space-m, 16px); + font-size: var(--openui-font-size-s, 13px); +} + +.playground .render-details summary { + cursor: pointer; + color: var(--openui-text-neutral-secondary, #666); +} + +.playground .render-details ul { + list-style: none; + margin-top: var(--openui-space-xs, 8px); +} + +.playground .render-crash { + border: 1px solid var(--openui-text-danger-primary, #a11); + border-radius: var(--openui-radius-m, 8px); + padding: var(--openui-space-m, 16px); + color: var(--openui-text-danger-primary, #a11); +} + +.playground .render-crash pre { + margin: var(--openui-space-xs, 8px) 0; + white-space: pre-wrap; + font-family: var(--paste-mono); +} + +/* ── tree panel ────────────────────────────────────────────────────────── */ + +.playground .tree-node { + margin-bottom: var(--openui-space-3xs, 2px); +} + +.playground .tree-header { + display: inline-flex; + align-items: center; + gap: var(--openui-space-2xs, 6px); + border: none; + background: none; + cursor: pointer; + color: inherit; + font: inherit; + padding: var(--openui-space-3xs, 2px) 0; +} + +.playground .tree-caret { + color: var(--openui-text-neutral-tertiary, #999); + display: inline-flex; + width: 1em; +} + +.playground .tree-type { + font-family: var(--paste-mono); + font-weight: 600; +} + +.playground .tree-props { + margin-left: var(--openui-space-l, 24px); + border-left: var(--paste-border); + padding-left: var(--openui-space-s, 10px); +} + +.playground .tree-prop { + display: flex; + gap: var(--openui-space-s, 10px); + align-items: baseline; + padding: 1px 0; +} + +.playground .tree-prop dt { + color: var(--openui-text-neutral-secondary, #666); + font-family: var(--paste-mono); + font-size: var(--openui-font-size-xs, 12px); + min-width: 80px; +} + +.playground .tree-children { + display: flex; + flex-direction: column; +} + +.playground .tree-scalar { + font-family: var(--paste-mono); + font-size: var(--openui-font-size-xs, 12px); + word-break: break-word; +} + +.playground .tree-expr { + font-family: var(--paste-mono); + font-size: var(--openui-font-size-xs, 12px); + color: var(--openui-text-info-primary, #147); +} + +.playground .tree-extra { + margin-top: var(--openui-space-l, 24px); +} + +.playground .tree-extra h3 { + font-size: var(--openui-font-size-s, 13px); + margin-bottom: var(--openui-space-xs, 8px); +} + +.playground .tree-json { + font-family: var(--paste-mono); + font-size: var(--openui-font-size-xs, 12px); + white-space: pre-wrap; + word-break: break-word; +} + +/* ── toasts ────────────────────────────────────────────────────────────── */ + +.playground .toast-stack { + position: fixed; + bottom: var(--openui-space-l, 24px); + right: var(--openui-space-l, 24px); + display: flex; + flex-direction: column; + gap: var(--openui-space-xs, 8px); + z-index: 100; +} + +.playground .toast-body { + display: inline-flex; + align-items: center; + gap: var(--openui-space-xs, 8px); +} + +.playground .toast-spinner { + width: 12px; + height: 12px; + border-radius: 50%; + border: 2px solid var(--openui-border-default, #ccc); + border-top-color: var(--openui-interactive-accent-default, #4467f6); + animation: paste-toast-spin 0.8s linear infinite; +} + +@keyframes paste-toast-spin { + to { + transform: rotate(360deg); + } +} + +/* ── stream timeline ───────────────────────────────────────────────────── */ + +.playground .convergence { + padding: var(--openui-space-xs, 8px) var(--openui-space-m, 16px); + border-radius: var(--openui-radius-s, 6px); + margin-bottom: var(--openui-space-m, 16px); + font-weight: 600; + display: flex; + align-items: center; + gap: var(--openui-space-2xs, 6px); +} + +.playground .convergence-converged { + background: var(--openui-success-background, #e7f7ea); + color: var(--openui-text-success-primary, #171); +} + +.playground .convergence-diverged { + background: var(--openui-danger-background, #ffecec); + color: var(--openui-text-danger-primary, #a11); +} + +.playground .trace-table { + width: 100%; + border-collapse: collapse; + font-size: var(--openui-font-size-xs, 12px); +} + +.playground .trace-table th { + text-align: left; + color: var(--openui-text-neutral-secondary, #666); + font-weight: 500; + padding: var(--openui-space-3xs, 2px) var(--openui-space-xs, 8px); + border-bottom: var(--paste-border); + position: sticky; + top: calc(-1 * var(--openui-space-m, 16px)); + background: var(--openui-background, #fff); +} + +.playground .trace-table td { + padding: var(--openui-space-3xs, 2px) var(--openui-space-xs, 8px); + border-bottom: var(--paste-border); + font-family: var(--paste-mono); + content-visibility: auto; +} + +.playground .root-cell { + display: flex; + align-items: center; + gap: var(--openui-space-2xs, 6px); +} + +.playground .row-appeared td { + background: var(--openui-success-background, #e7f7ea); +} + +.playground .row-dropped td { + background: var(--openui-danger-background, #ffecec); +} + +.playground .chunk-preview { + white-space: pre; + max-width: 260px; + overflow: hidden; + text-overflow: ellipsis; + display: inline-block; + vertical-align: bottom; +} + +/* ── hover signature tooltip (VSCode-style) ────────────────────────────── */ + +.playground .editor-pane .cm-tooltip { + border: none; + background: transparent; +} + +.playground .cm-signature-tooltip { + /* popover token is fully opaque; --openui-elevated is a translucent tint */ + background: var(--openui-popover-background, var(--openui-background, #fff)); + border: var(--paste-border); + border-radius: var(--openui-radius-m, 8px); + box-shadow: var(--openui-shadow-l, 0 6px 24px rgba(0, 0, 0, 0.12)); + padding: var(--openui-space-xs, 8px) var(--openui-space-s, 10px); + max-width: 480px; +} + +.playground .cm-signature-code { + font-family: var(--paste-mono); + font-size: var(--openui-font-size-xs, 12px); + white-space: pre-wrap; + word-break: break-word; +} + +.playground .cm-signature-doc { + margin-top: var(--openui-space-2xs, 6px); + color: var(--openui-text-neutral-secondary, #666); + font-size: var(--openui-font-size-xs, 12px); +} + +/* ── back to docs link ─────────────────────────────────────────────────── */ + +.playground .back-link { + display: inline-flex; + align-items: center; + gap: var(--openui-space-2xs, 6px); + color: var(--openui-text-neutral-secondary, #666); + text-decoration: none; + font-size: var(--openui-font-size-s, 13px); + margin-right: var(--openui-space-s, 10px); +} + +.playground .back-link:hover { + color: var(--openui-text-neutral-primary, #111); +} + +/* ── two-row header ────────────────────────────────────────────────────── */ + +.playground .topbar { + display: flex; + align-items: center; + justify-content: space-between; + padding: var(--openui-space-s, 10px) var(--openui-space-m, 16px); + border-bottom: var(--paste-border); +} + +.playground .topbar .toolbar-brand { + gap: var(--openui-space-s, 10px); +} + +/* Streaming trace: fixed table layout so columns don't re-expand as rows + stream in (auto layout + content-visibility made widths jump until a + tab-switch forced a full re-layout). */ +.playground .trace-table { + table-layout: fixed; +} +.playground .trace-table th:nth-child(1) { + width: 36px; +} +.playground .trace-table th:nth-child(3) { + width: 60px; +} +.playground .trace-table th:nth-child(4) { + width: 96px; +} +.playground .trace-table th:nth-child(5) { + width: 52px; +} +.playground .trace-table th:nth-child(6) { + width: 88px; +} +.playground .trace-table th:nth-child(7) { + width: 52px; +} +.playground .trace-table th:nth-child(8) { + width: 46px; +} +.playground .trace-table td { + content-visibility: visible; + overflow: hidden; + text-overflow: ellipsis; +} +.playground .chunk-preview { + max-width: 100%; +} + +/* ── mock tools note ───────────────────────────────────────────────────── */ + +.playground .mock-note { + display: flex; + align-items: flex-start; + gap: var(--openui-space-2xs, 6px); + padding: var(--openui-space-2xs, 6px) var(--openui-space-m, 16px); + color: var(--openui-text-info-primary, #147); + background: var(--openui-info-background, #e8f2ff); + font-size: var(--openui-font-size-xs, 12px); + border-radius: var(--openui-space-s); +} + +.playground .mock-note svg { + flex-shrink: 0; + margin-top: 2px; +} + +/* ── topbar right cluster (title + theme toggle) ───────────────────────── */ + +.playground .topbar-right { + display: flex; + align-items: center; + gap: var(--openui-space-s, 10px); +} + +/* ── playback button group (icon-only, joined) ─────────────────────────── */ + +.playground .btn-group { + display: inline-flex; + align-items: stretch; +} + +/* Collapse inner radii/borders so the three buttons read as one control. */ +.playground .btn-group > button { + padding-inline: var(--openui-space-s, 10px); +} + +.playground .btn-group > button:not(:first-child) { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + margin-left: -1px; +} + +.playground .btn-group > button:not(:last-child) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} + +/* ── responsive ────────────────────────────────────────────────────────── */ + +.playground { + /* dvh keeps the layout inside the visible viewport under mobile browser chrome */ + height: 100dvh; +} + +@media (max-width: 860px) { + .playground .topbar, + .playground .toolbar { + padding: var(--openui-space-xs, 8px) var(--openui-space-s, 10px); + } + + .playground .toolbar-controls { + gap: var(--openui-space-s, 10px); + } + + /* Vertical split: the separator becomes a horizontal grab bar. */ + .playground .resize-handle { + width: auto; + height: 5px; + border-left: none; + border-top: var(--paste-border); + } + + .playground .split-panel { + height: 100%; + width: 100%; + } + + .playground .help-dialog { + max-width: 100%; + max-height: 92dvh; + } + + .playground .help-header, + .playground .help-body { + padding: var(--openui-space-s, 10px) var(--openui-space-m, 16px); + } + + .playground .toast-stack { + left: var(--openui-space-s, 10px); + right: var(--openui-space-s, 10px); + bottom: var(--openui-space-s, 10px); + } + + .playground .render-canvas { + padding: var(--openui-space-s, 10px); + } +} + +/* ── mobile settings drawer ────────────────────────────────────────────── */ + +.playground .drawer-overlay { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.4); + display: flex; + align-items: flex-end; + z-index: 200; +} + +.playground .drawer { + background: var(--openui-background, #fff); + border-top: var(--paste-border); + border-radius: var(--openui-radius-l, 12px) var(--openui-radius-l, 12px) 0 0; + box-shadow: var(--openui-shadow-2xl, 0 -12px 48px rgba(0, 0, 0, 0.25)); + width: 100%; + max-height: 80dvh; + display: flex; + flex-direction: column; +} + +.playground .drawer-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: var(--openui-space-s, 10px) var(--openui-space-m, 16px); + border-bottom: var(--paste-border); +} + +.playground .drawer-header h2 { + font-size: var(--openui-font-size-m, 14px); + font-weight: 700; +} + +.playground .drawer-body { + padding: var(--openui-space-m, 16px); + overflow-y: auto; + display: flex; + flex-direction: column; + gap: var(--openui-space-m, 16px); +} + +/* Fields become full-width label/control rows inside the drawer. */ +.playground .drawer-body .toolbar-field { + justify-content: space-between; + width: 100%; +} + +/* Empty states are prose, never flex. Guards against the docs bundle's own + unscoped .panel-empty rule (display:flex + height:100vh) that collides + with this class name and split the text into separate flex boxes. */ +.playground .panel-empty { + display: block; + height: auto; +} + +/* Tailwind preflight (docs-global) strips list-style from ul/ol — restore + markers inside the help dialog's prose lists. */ +.playground .help-body ul { + list-style: disc; +} + +.playground .help-body ol { + list-style: decimal; +} + +.playground .help-body li { + display: list-item; +} diff --git a/docs/components/site-primary-nav.tsx b/docs/components/site-primary-nav.tsx index 440bdfd7f..559781606 100644 --- a/docs/components/site-primary-nav.tsx +++ b/docs/components/site-primary-nav.tsx @@ -41,6 +41,11 @@ export const PRIMARY_SITE_NAV_ITEMS: NavItem[] = [ description: "Try OpenUI OSS and Cloud in a live chat.", href: "/chat", }, + { + title: "Paste", + description: "Validate and stream OpenUI Lang.", + href: "/paste", + }, { title: "Dashboard Demo", description: "Testdrive a dashboard building experience.", diff --git a/docs/package.json b/docs/package.json index 581d573bd..1766b6041 100644 --- a/docs/package.json +++ b/docs/package.json @@ -42,7 +42,13 @@ "react-dom": "^19.2.4", "tailwind-merge": "^3.4.1", "zod": "^4.3.6", - "zustand": "4.5.7" + "zustand": "4.5.7", + "@codemirror/language": "^6.12.4", + "@codemirror/state": "^6.7.1", + "@codemirror/view": "^6.43.6", + "@uiw/react-codemirror": "^4.25.11", + "react-resizable-panels": "^4.12.2", + "semver": "^7.8.5" }, "devDependencies": { "@tailwindcss/postcss": "^4.1.18", @@ -52,6 +58,7 @@ "@types/react-dom": "^19.2.3", "eslint-config-next": "16.2.6", "postcss": "^8.5.6", - "tailwindcss": "^4.1.18" + "tailwindcss": "^4.1.18", + "@types/semver": "^7.7.1" } } diff --git a/docs/tsconfig.json b/docs/tsconfig.json index 6f43c231e..95cfd3874 100644 --- a/docs/tsconfig.json +++ b/docs/tsconfig.json @@ -2,7 +2,11 @@ "compilerOptions": { "baseUrl": ".", "target": "ESNext", - "lib": ["dom", "dom.iterable", "esnext"], + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], "allowJs": true, "skipLibCheck": true, "strict": true, @@ -16,10 +20,21 @@ "jsx": "react-jsx", "incremental": true, "paths": { - "@components/*": ["./app/components/*"], - "@/*": ["./*"], - "@design-system/*": ["./shared/design-system/*"], - "fumadocs-mdx:collections/*": [".source/*"] + "@components/*": [ + "./app/components/*" + ], + "@/*": [ + "./*" + ], + "@design-system/*": [ + "./shared/design-system/*" + ], + "fumadocs-mdx:collections/*": [ + ".source/*" + ], + "@paste/*": [ + "./app/paste/*" + ] }, "plugins": [ { @@ -34,5 +49,7 @@ ".next/types/**/*.ts", ".next/dev/types/**/*.ts" ], - "exclude": ["node_modules"] + "exclude": [ + "node_modules" + ] } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 914e86164..b0f19d17e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -126,6 +126,15 @@ importers: docs: dependencies: + '@codemirror/language': + specifier: ^6.12.4 + version: 6.12.4 + '@codemirror/state': + specifier: ^6.7.1 + version: 6.7.1 + '@codemirror/view': + specifier: ^6.43.6 + version: 6.43.6 '@openuidev/cli': specifier: workspace:* version: link:../packages/openui-cli @@ -159,6 +168,9 @@ importers: '@takumi-rs/image-response': specifier: ^0.68.17 version: 0.68.17 + '@uiw/react-codemirror': + specifier: ^4.25.11 + version: 4.25.11(@babel/runtime@7.29.7)(@codemirror/autocomplete@6.20.3)(@codemirror/language@6.12.4)(@codemirror/lint@6.9.7)(@codemirror/search@6.7.1)(@codemirror/state@6.7.1)(@codemirror/theme-one-dark@6.1.3)(@codemirror/view@6.43.6)(codemirror@6.0.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) fumadocs-core: specifier: 16.9.1 version: 16.9.1(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@0.570.0(react@19.2.4))(next@16.2.6(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.89.2))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.3.6) @@ -201,6 +213,12 @@ importers: react-dom: specifier: ^19.2.4 version: 19.2.4(react@19.2.4) + react-resizable-panels: + specifier: ^4.12.2 + version: 4.12.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + semver: + specifier: ^7.8.5 + version: 7.8.5 tailwind-merge: specifier: ^3.4.1 version: 3.5.0 @@ -226,12 +244,15 @@ importers: '@types/react-dom': specifier: ^19.2.3 version: 19.2.3(@types/react@19.2.14) + '@types/semver': + specifier: ^7.7.1 + version: 7.7.1 eslint-config-next: specifier: 16.2.6 version: 16.2.6(@typescript-eslint/parser@8.59.4(eslint@9.29.0(jiti@2.7.0))(typescript@5.9.3))(eslint@9.29.0(jiti@2.7.0))(typescript@5.9.3) postcss: specifier: '>=8.5.10' - version: 8.5.15 + version: 8.5.16 tailwindcss: specifier: ^4.1.18 version: 4.2.1 @@ -1848,7 +1869,7 @@ importers: version: 8.6.18(storybook@8.6.18(prettier@3.9.4)) '@storybook/addon-styling-webpack': specifier: ^1.0.1 - version: 1.0.1(storybook@8.6.18(prettier@3.9.4))(webpack@5.107.2(esbuild@0.25.12)(lightningcss@1.32.0)(postcss@8.5.15)) + version: 1.0.1(storybook@8.6.18(prettier@3.9.4))(webpack@5.107.2(esbuild@0.25.12)(lightningcss@1.32.0)(postcss@8.5.16)) '@storybook/addon-themes': specifier: ^8.5.3 version: 8.6.18(storybook@8.6.18(prettier@3.9.4)) @@ -1923,13 +1944,13 @@ importers: version: 4.0.5 postcss: specifier: '>=8.5.10' - version: 8.5.15 + version: 8.5.16 postcss-js: specifier: ^4.0.1 - version: 4.0.1(postcss@8.5.15) + version: 4.0.1(postcss@8.5.16) postcss-remove-duplicate-values: specifier: ^1.0.0 - version: 1.0.0(postcss@8.5.15) + version: 1.0.0(postcss@8.5.16) prettier-plugin-organize-imports: specifier: ^3.2.4 version: 3.2.4(prettier@3.9.4)(typescript@5.9.3) @@ -1959,7 +1980,7 @@ importers: version: 4.1.7(@opentelemetry/api@1.9.1)(@types/node@22.19.19)(jsdom@29.1.1)(vite@6.4.2(@types/node@22.19.19)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.48.0)(tsx@4.20.3)(yaml@2.9.0)) webpack: specifier: ^5.104.1 - version: 5.107.2(esbuild@0.25.12)(lightningcss@1.32.0)(postcss@8.5.15) + version: 5.107.2(esbuild@0.25.12)(lightningcss@1.32.0)(postcss@8.5.16) packages/svelte-lang: dependencies: @@ -3108,6 +3129,30 @@ packages: resolution: {integrity: sha512-SIOD2DxrRRwQ+jgzlXCqoEFiKOFqaPjhnNTGKXSRLvp1HiOvapLaFG2kEr9dYQTYe8rKrd9uvDUzmAITeNyaHQ==} engines: {node: '>=18.0.0'} + '@codemirror/autocomplete@6.20.3': + resolution: {integrity: sha512-tlosUqb+3BbxCxZdu4tKeRghPFC+QM7q4X5YhKV2eCmPG+1r2F3f4AaSz5sCrFqUtX4Jh20VFTKecl16MgiV9g==} + + '@codemirror/commands@6.10.4': + resolution: {integrity: sha512-Ryk9y9T0FFVF0cUGhAknveAyUOl/A1qReTFi+qPKtOh2Z9F4AUBz3XOrYD4ZEgZirdugVzHvd/2/Wcwy5OliTg==} + + '@codemirror/language@6.12.4': + resolution: {integrity: sha512-1q4PaT+o6PbgpkJt4Q8Fv5XJxTy4FUZ4MWETtyiDw3J0Pyr9E2vqcKL+k9wcvjNTIsauxvE7OfmWj3FRPHQ76A==} + + '@codemirror/lint@6.9.7': + resolution: {integrity: sha512-28/+iWLYxKxsvGYhSYL7zaCZqLz5+FFFDq9tVsvGv9kv8RY4fFAchJ5WX9M3YrrRlTIsECjsXPqeNgnSmNP2dg==} + + '@codemirror/search@6.7.1': + resolution: {integrity: sha512-uMe5UO6PamJtSHrXhhHOzSX3ReWtiJrva6GnPMwSOrZtiExb5X5eExhr2OUZQVvdxPsKpY3Ro2mFbQadpPWmHA==} + + '@codemirror/state@6.7.1': + resolution: {integrity: sha512-9QzNDgE4EYDnAHfrTlR2lwiPciiOymLtwKK+8yHQzCc7GXhAP9xdEbEJFy2IWB1j9UGUl9BsgMmTo/ImA02T7A==} + + '@codemirror/theme-one-dark@6.1.3': + resolution: {integrity: sha512-NzBdIvEJmx6fjeremiGp3t/okrLPYT0d9orIc7AFun8oZcRk58aejkqhv6spnz4MLAevrKNPMQYXEWMg4s+sKA==} + + '@codemirror/view@6.43.6': + resolution: {integrity: sha512-EVunGSYN1wz1p75WY1s3Xg7t3i8Yol0kGZGizNdX9BUFgMFILYVe8/u6EVpo7Ff5PwbZuILb4QAq7IZoKzIEQA==} + '@colordx/core@5.4.3': resolution: {integrity: sha512-kIxYSfA5T8HXjav55UaaH/o/cKivF6jCCGIb8eqtcsfI46wsvlSiT8jMDyrl779qLec3c2c2oHBZo4oAhvbjrQ==} @@ -4752,6 +4797,15 @@ packages: '@langchain/protocol@0.0.18': resolution: {integrity: sha512-XW1egQtPfsGI41w2AMZNFZrUIwFSQHTjVMZs0OaTpCAvht/QLoaPN8FQcsysMVypOhupG28J29yOorrc70otBQ==} + '@lezer/common@1.5.2': + resolution: {integrity: sha512-sxQE460fPZyU3sdc8lafxiPwJHBzZRy/udNFynGQky1SePYBdhkBl1kOagA9uT3pxR8K09bOrmTUqA9wb/PjSQ==} + + '@lezer/highlight@1.2.3': + resolution: {integrity: sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==} + + '@lezer/lr@1.4.10': + resolution: {integrity: sha512-rnCpTIBafOx4mRp43xOxDJbFipJm/c0cia/V5TiGlhmMa+wsSdoGmUN3w5Bqrks/09Q/D4tNAmWaT8p6NRi77A==} + '@loaderkit/resolve@1.0.4': resolution: {integrity: sha512-rJzYKVcV4dxJv+vW6jlvagF8zvGxHJ2+HTr1e2qOejfmGhAApgJHl8Aog4mMszxceTRiKTTbnpgmTO1bEZHV/A==} @@ -4768,6 +4822,9 @@ packages: engines: {node: '>=18'} hasBin: true + '@marijn/find-cluster-break@1.0.3': + resolution: {integrity: sha512-FY+MKLBoTsLNJF/eLWaOsXGdz6uh3Iu1axjPf6TUq92IYumcTcXWHoS747JARLkcdlJ/Waiaxc5wQfFO8jC6NA==} + '@mariozechner/clipboard-darwin-arm64@0.3.9': resolution: {integrity: sha512-BfgV7vCEWZwJwZJw03r6bP5+tf0iI/ANuQYCxi9RNn7FrWB3yzGuMKCrNLRl6V761vXRdL8+OqZ0wd4TqlsNOQ==} engines: {node: '>= 10'} @@ -10057,6 +10114,28 @@ packages: resolution: {integrity: sha512-JVUD8X2tfDMWjcjLs4yVxxVrS8yR5vnh386GAXT9Qj79nBxxXSaHFQZg5FweLmT8HlPQ3kii6noUB+Z9RN7DvQ==} engines: {node: '>=22.0.0'} + '@uiw/codemirror-extensions-basic-setup@4.25.11': + resolution: {integrity: sha512-otyFa+n9IOYtEjaKOxPedHkj15fTPUF21wdR9pv0GpZPfuGl27cvmcv6+tognbRu9VvEcsHKE+ESoszeo3KfTw==} + peerDependencies: + '@codemirror/autocomplete': '>=6.0.0' + '@codemirror/commands': '>=6.0.0' + '@codemirror/language': '>=6.0.0' + '@codemirror/lint': '>=6.0.0' + '@codemirror/search': '>=6.0.0' + '@codemirror/state': '>=6.0.0' + '@codemirror/view': '>=6.0.0' + + '@uiw/react-codemirror@4.25.11': + resolution: {integrity: sha512-DYVFAKLX+F/4JS9N/7xexh+TICrlncwkX9HKKInrP1bwO0tSfc3k0GB6oawTYhelVKh20cX3TuRx+NJSkVXuMw==} + peerDependencies: + '@babel/runtime': '>=7.11.0' + '@codemirror/state': '>=6.0.0' + '@codemirror/theme-one-dark': '>=6.0.0' + '@codemirror/view': '>=6.0.0' + codemirror: '>=6.0.0' + react: '>=17.0.0' + react-dom: '>=17.0.0' + '@ungap/structured-clone@1.3.1': resolution: {integrity: sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==} @@ -11350,6 +11429,9 @@ packages: code-block-writer@13.0.3: resolution: {integrity: sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg==} + codemirror@6.0.2: + resolution: {integrity: sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==} + collapse-white-space@2.1.0: resolution: {integrity: sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==} @@ -13216,10 +13298,6 @@ packages: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} - get-east-asian-width@1.5.0: - resolution: {integrity: sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==} - engines: {node: '>=18'} - get-east-asian-width@1.6.0: resolution: {integrity: sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==} engines: {node: '>=18'} @@ -13263,9 +13341,6 @@ packages: resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} engines: {node: '>= 0.4'} - get-tsconfig@4.10.1: - resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==} - get-tsconfig@4.14.0: resolution: {integrity: sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==} @@ -13741,9 +13816,6 @@ packages: resolution: {integrity: sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - inline-style-parser@0.2.4: - resolution: {integrity: sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==} - inline-style-parser@0.2.7: resolution: {integrity: sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==} @@ -14796,9 +14868,6 @@ packages: mdast-util-find-and-replace@3.0.2: resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==} - mdast-util-from-markdown@2.0.2: - resolution: {integrity: sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==} - mdast-util-from-markdown@2.0.3: resolution: {integrity: sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==} @@ -16413,10 +16482,6 @@ packages: postcss-value-parser@4.2.0: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} - postcss@8.5.15: - resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==} - engines: {node: ^10 || ^12 || >=14} - postcss@8.5.16: resolution: {integrity: sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==} engines: {node: ^10 || ^12 || >=14} @@ -16879,6 +16944,12 @@ packages: '@types/react': optional: true + react-resizable-panels@4.12.2: + resolution: {integrity: sha512-NwY5LCo4WrxVvDh0xoMML6EMLPONP/8ckKcIdpnojxexoatZdjLiRqLJQjQK5CPkd4SYiB/2M5BVrjZBQtOO7Q==} + peerDependencies: + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + react-smooth@4.0.4: resolution: {integrity: sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q==} peerDependencies: @@ -17331,11 +17402,6 @@ packages: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true - semver@7.7.4: - resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} - engines: {node: '>=10'} - hasBin: true - semver@7.8.0: resolution: {integrity: sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==} engines: {node: '>=10'} @@ -17346,6 +17412,11 @@ packages: engines: {node: '>=10'} hasBin: true + semver@7.8.5: + resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} + engines: {node: '>=10'} + hasBin: true + send@0.19.2: resolution: {integrity: sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==} engines: {node: '>= 0.8.0'} @@ -17743,8 +17814,8 @@ packages: stubs@3.0.0: resolution: {integrity: sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==} - style-to-js@1.1.17: - resolution: {integrity: sha512-xQcBGDxJb6jjFCTzvQtfiPn6YvvP2O8U1MDIPNfJQlWMYfktPy+iGsHE7cssjs7y84d9fQaK4UF3RIJaAHSoYA==} + style-mod@4.1.3: + resolution: {integrity: sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==} style-to-js@1.1.21: resolution: {integrity: sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==} @@ -17752,9 +17823,6 @@ packages: style-to-object@1.0.14: resolution: {integrity: sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==} - style-to-object@1.0.9: - resolution: {integrity: sha512-G4qppLgKu/k6FwRpHiGiKPaPTFcG3g4wNVX/Qsfu+RqQM30E7Tyu/TEgxcL9PNLF5pdRLwQdE3YKKf+KF2Dzlw==} - styled-jsx@5.1.6: resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==} engines: {node: '>= 12.0.0'} @@ -18693,9 +18761,6 @@ packages: vfile-location@5.0.3: resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==} - vfile-message@4.0.2: - resolution: {integrity: sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==} - vfile-message@4.0.3: resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} @@ -19096,10 +19161,6 @@ packages: resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} engines: {node: '>= 0.4'} - which-typed-array@1.1.19: - resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==} - engines: {node: '>= 0.4'} - which-typed-array@1.1.20: resolution: {integrity: sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==} engines: {node: '>= 0.4'} @@ -19683,7 +19744,7 @@ snapshots: commander: 10.0.1 marked: 9.1.6 marked-terminal: 7.3.0(marked@9.1.6) - semver: 7.7.4 + semver: 7.8.1 '@arethetypeswrong/core@0.18.2': dependencies: @@ -19691,8 +19752,8 @@ snapshots: '@loaderkit/resolve': 1.0.4 cjs-module-lexer: 1.4.3 fflate: 0.8.2 - lru-cache: 11.2.7 - semver: 7.7.4 + lru-cache: 11.5.0 + semver: 7.8.1 typescript: 5.6.1-rc validate-npm-package-name: 5.0.1 @@ -20881,6 +20942,59 @@ snapshots: '@cloudflare/kv-asset-handler@0.4.2': {} + '@codemirror/autocomplete@6.20.3': + dependencies: + '@codemirror/language': 6.12.4 + '@codemirror/state': 6.7.1 + '@codemirror/view': 6.43.6 + '@lezer/common': 1.5.2 + + '@codemirror/commands@6.10.4': + dependencies: + '@codemirror/language': 6.12.4 + '@codemirror/state': 6.7.1 + '@codemirror/view': 6.43.6 + '@lezer/common': 1.5.2 + + '@codemirror/language@6.12.4': + dependencies: + '@codemirror/state': 6.7.1 + '@codemirror/view': 6.43.6 + '@lezer/common': 1.5.2 + '@lezer/highlight': 1.2.3 + '@lezer/lr': 1.4.10 + style-mod: 4.1.3 + + '@codemirror/lint@6.9.7': + dependencies: + '@codemirror/state': 6.7.1 + '@codemirror/view': 6.43.6 + crelt: 1.0.7 + + '@codemirror/search@6.7.1': + dependencies: + '@codemirror/state': 6.7.1 + '@codemirror/view': 6.43.6 + crelt: 1.0.7 + + '@codemirror/state@6.7.1': + dependencies: + '@marijn/find-cluster-break': 1.0.3 + + '@codemirror/theme-one-dark@6.1.3': + dependencies: + '@codemirror/language': 6.12.4 + '@codemirror/state': 6.7.1 + '@codemirror/view': 6.43.6 + '@lezer/highlight': 1.2.3 + + '@codemirror/view@6.43.6': + dependencies: + '@codemirror/state': 6.7.1 + crelt: 1.0.7 + style-mod: 4.1.3 + w3c-keyname: 2.2.8 + '@colordx/core@5.4.3': {} '@colors/colors@1.5.0': @@ -21741,7 +21855,7 @@ snapshots: jsc-safe-url: 0.2.4 lightningcss: 1.32.0 picomatch: 4.0.4 - postcss: 8.5.15 + postcss: 8.5.16 resolve-from: 5.0.0 optionalDependencies: expo: 54.0.34(@babel/core@7.29.7)(graphql@16.14.0)(react-native@0.83.2(@babel/core@7.29.7)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) @@ -21889,9 +22003,9 @@ snapshots: '@gar/promisify@1.1.3': optional: true - '@google-cloud/opentelemetry-cloud-monitoring-exporter@0.21.0(@opentelemetry/api@1.9.0)(@opentelemetry/core@2.9.0(@opentelemetry/api@1.9.1))(@opentelemetry/resources@2.7.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-metrics@2.2.0(@opentelemetry/api@1.9.0))(encoding@0.1.13)': + '@google-cloud/opentelemetry-cloud-monitoring-exporter@0.21.0(@opentelemetry/api@1.9.0)(@opentelemetry/core@2.9.0(@opentelemetry/api@1.9.1))(@opentelemetry/resources@2.7.1(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-metrics@2.2.0(@opentelemetry/api@1.9.1))(encoding@0.1.13)': dependencies: - '@google-cloud/opentelemetry-resource-util': 3.0.0(@opentelemetry/core@2.9.0(@opentelemetry/api@1.9.1))(@opentelemetry/resources@2.7.1(@opentelemetry/api@1.9.0))(encoding@0.1.13) + '@google-cloud/opentelemetry-resource-util': 3.0.0(@opentelemetry/core@2.9.0(@opentelemetry/api@1.9.1))(@opentelemetry/resources@2.7.1(@opentelemetry/api@1.9.1))(encoding@0.1.13) '@google-cloud/precise-date': 4.0.0 '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 2.9.0(@opentelemetry/api@1.9.1) @@ -21903,9 +22017,9 @@ snapshots: - encoding - supports-color - '@google-cloud/opentelemetry-cloud-trace-exporter@3.0.0(@opentelemetry/api@1.9.0)(@opentelemetry/core@2.9.0(@opentelemetry/api@1.9.1))(@opentelemetry/resources@2.7.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))(encoding@0.1.13)': + '@google-cloud/opentelemetry-cloud-trace-exporter@3.0.0(@opentelemetry/api@1.9.0)(@opentelemetry/core@2.9.0(@opentelemetry/api@1.9.1))(@opentelemetry/resources@2.7.1(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.1))(encoding@0.1.13)': dependencies: - '@google-cloud/opentelemetry-resource-util': 3.0.0(@opentelemetry/core@2.9.0(@opentelemetry/api@1.9.1))(@opentelemetry/resources@2.7.1(@opentelemetry/api@1.9.0))(encoding@0.1.13) + '@google-cloud/opentelemetry-resource-util': 3.0.0(@opentelemetry/core@2.9.0(@opentelemetry/api@1.9.1))(@opentelemetry/resources@2.7.1(@opentelemetry/api@1.9.1))(encoding@0.1.13) '@grpc/grpc-js': 1.14.4 '@grpc/proto-loader': 0.8.1 '@opentelemetry/api': 1.9.0 @@ -21917,7 +22031,7 @@ snapshots: - encoding - supports-color - '@google-cloud/opentelemetry-resource-util@3.0.0(@opentelemetry/core@2.9.0(@opentelemetry/api@1.9.1))(@opentelemetry/resources@2.7.1(@opentelemetry/api@1.9.0))(encoding@0.1.13)': + '@google-cloud/opentelemetry-resource-util@3.0.0(@opentelemetry/core@2.9.0(@opentelemetry/api@1.9.1))(@opentelemetry/resources@2.7.1(@opentelemetry/api@1.9.1))(encoding@0.1.13)': dependencies: '@opentelemetry/core': 2.9.0(@opentelemetry/api@1.9.1) '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.0) @@ -21972,8 +22086,8 @@ snapshots: '@google/adk@1.3.0(@bufbuild/protobuf@2.12.0)(@cfworker/json-schema@4.1.1)(@grpc/grpc-js@1.14.4)(@mikro-orm/mariadb@6.6.15(@mikro-orm/core@6.6.15)(pg@8.20.0))(@mikro-orm/mssql@6.6.15(@mikro-orm/core@6.6.15)(mariadb@3.4.5)(pg@8.20.0))(@mikro-orm/mysql@6.6.15(@mikro-orm/core@6.6.15)(@types/node@22.19.19)(mariadb@3.4.5)(pg@8.20.0))(@mikro-orm/postgresql@6.6.15(@mikro-orm/core@6.6.15)(mariadb@3.4.5))(@mikro-orm/sqlite@6.6.15(@mikro-orm/core@6.6.15)(mariadb@3.4.5)(pg@8.20.0))(@opentelemetry/core@2.9.0(@opentelemetry/api@1.9.1))(encoding@0.1.13)': dependencies: '@a2a-js/sdk': 0.3.14(@bufbuild/protobuf@2.12.0)(@grpc/grpc-js@1.14.4)(express@4.22.2) - '@google-cloud/opentelemetry-cloud-monitoring-exporter': 0.21.0(@opentelemetry/api@1.9.0)(@opentelemetry/core@2.9.0(@opentelemetry/api@1.9.1))(@opentelemetry/resources@2.7.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-metrics@2.2.0(@opentelemetry/api@1.9.0))(encoding@0.1.13) - '@google-cloud/opentelemetry-cloud-trace-exporter': 3.0.0(@opentelemetry/api@1.9.0)(@opentelemetry/core@2.9.0(@opentelemetry/api@1.9.1))(@opentelemetry/resources@2.7.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))(encoding@0.1.13) + '@google-cloud/opentelemetry-cloud-monitoring-exporter': 0.21.0(@opentelemetry/api@1.9.0)(@opentelemetry/core@2.9.0(@opentelemetry/api@1.9.1))(@opentelemetry/resources@2.7.1(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-metrics@2.2.0(@opentelemetry/api@1.9.1))(encoding@0.1.13) + '@google-cloud/opentelemetry-cloud-trace-exporter': 3.0.0(@opentelemetry/api@1.9.0)(@opentelemetry/core@2.9.0(@opentelemetry/api@1.9.1))(@opentelemetry/resources@2.7.1(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.1))(encoding@0.1.13) '@google-cloud/storage': 7.21.0(encoding@0.1.13) '@google-cloud/vertexai': 1.12.0(@modelcontextprotocol/sdk@1.29.0(@cfworker/json-schema@4.1.1)(zod@4.4.3))(encoding@0.1.13) '@google/genai': 1.52.0(@modelcontextprotocol/sdk@1.29.0(@cfworker/json-schema@4.1.1)(zod@4.4.3)) @@ -22810,6 +22924,16 @@ snapshots: '@langchain/protocol@0.0.18': {} + '@lezer/common@1.5.2': {} + + '@lezer/highlight@1.2.3': + dependencies: + '@lezer/common': 1.5.2 + + '@lezer/lr@1.4.10': + dependencies: + '@lezer/common': 1.5.2 + '@loaderkit/resolve@1.0.4': dependencies: '@braidai/lang': 1.1.2 @@ -22833,6 +22957,8 @@ snapshots: - encoding - supports-color + '@marijn/find-cluster-break@1.0.3': {} + '@mariozechner/clipboard-darwin-arm64@0.3.9': optional: true @@ -23104,7 +23230,7 @@ snapshots: '@mikro-orm/mariadb@6.6.15(@mikro-orm/core@6.6.15)(pg@8.20.0)': dependencies: '@mikro-orm/core': 6.6.15 - '@mikro-orm/knex': 6.6.15(@mikro-orm/core@6.6.15)(mariadb@3.4.5)(pg@8.20.0)(sqlite3@5.1.7) + '@mikro-orm/knex': 6.6.15(@mikro-orm/core@6.6.15)(mariadb@3.4.5)(mysql2@3.20.0(@types/node@22.19.19))(pg@8.20.0) mariadb: 3.4.5 transitivePeerDependencies: - better-sqlite3 @@ -23157,7 +23283,7 @@ snapshots: '@mikro-orm/postgresql@6.6.15(@mikro-orm/core@6.6.15)(mariadb@3.4.5)': dependencies: '@mikro-orm/core': 6.6.15 - '@mikro-orm/knex': 6.6.15(@mikro-orm/core@6.6.15)(mariadb@3.4.5)(pg@8.20.0)(sqlite3@5.1.7) + '@mikro-orm/knex': 6.6.15(@mikro-orm/core@6.6.15)(mariadb@3.4.5)(mysql2@3.20.0(@types/node@22.19.19))(pg@8.20.0) pg: 8.20.0 postgres-array: 3.0.4 postgres-date: 2.1.0 @@ -23582,7 +23708,7 @@ snapshots: '@npmcli/fs@1.1.1': dependencies: '@gar/promisify': 1.1.3 - semver: 7.8.1 + semver: 7.8.5 optional: true '@npmcli/move-file@1.1.2': @@ -23833,9 +23959,9 @@ snapshots: '@rollup/plugin-replace': 6.0.3(rollup@4.60.4) '@vitejs/plugin-vue': 6.0.7(vite@7.3.3(@types/node@25.3.2)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.48.0)(tsx@4.20.3)(yaml@2.9.0))(vue@3.5.34(typescript@5.9.3)) '@vitejs/plugin-vue-jsx': 5.1.5(vite@7.3.3(@types/node@25.3.2)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.48.0)(tsx@4.20.3)(yaml@2.9.0))(vue@3.5.34(typescript@5.9.3)) - autoprefixer: 10.5.0(postcss@8.5.15) + autoprefixer: 10.5.0(postcss@8.5.16) consola: 3.4.2 - cssnano: 7.1.9(postcss@8.5.15) + cssnano: 7.1.9(postcss@8.5.16) defu: 6.1.7 escape-string-regexp: 5.0.0 exsolve: 1.0.8 @@ -23852,7 +23978,7 @@ snapshots: pathe: 2.0.3 perfect-debounce: 2.1.0 pkg-types: 2.3.1 - postcss: 8.5.15 + postcss: 8.5.16 seroval: 1.5.4 std-env: 4.1.0 ufo: 1.6.4 @@ -28452,10 +28578,10 @@ snapshots: storybook: 8.6.18(prettier@3.9.4) ts-dedent: 2.2.0 - '@storybook/addon-styling-webpack@1.0.1(storybook@8.6.18(prettier@3.9.4))(webpack@5.107.2(esbuild@0.25.12)(lightningcss@1.32.0)(postcss@8.5.15))': + '@storybook/addon-styling-webpack@1.0.1(storybook@8.6.18(prettier@3.9.4))(webpack@5.107.2(esbuild@0.25.12)(lightningcss@1.32.0)(postcss@8.5.16))': dependencies: '@storybook/node-logger': 8.6.14(storybook@8.6.18(prettier@3.9.4)) - webpack: 5.107.2(esbuild@0.25.12)(lightningcss@1.32.0)(postcss@8.5.15) + webpack: 5.107.2(esbuild@0.25.12)(lightningcss@1.32.0)(postcss@8.5.16) transitivePeerDependencies: - storybook @@ -28726,7 +28852,7 @@ snapshots: chokidar: 5.0.0 kleur: 4.1.5 sade: 1.8.1 - semver: 7.7.4 + semver: 7.8.1 svelte: 5.55.9(@typescript-eslint/types@8.59.4) svelte2tsx: 0.7.52(svelte@5.55.9(@typescript-eslint/types@8.59.4))(typescript@5.9.3) transitivePeerDependencies: @@ -28911,7 +29037,7 @@ snapshots: '@alloc/quick-lru': 5.2.0 '@tailwindcss/node': 4.2.1 '@tailwindcss/oxide': 4.2.1 - postcss: 8.5.15 + postcss: 8.5.16 tailwindcss: 4.2.1 '@tailwindcss/vite@4.2.2(vite@6.4.2(@types/node@25.3.2)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.48.0)(tsx@4.20.3)(yaml@2.9.0))': @@ -29663,7 +29789,7 @@ snapshots: debug: 4.4.3 minimatch: 10.2.5 semver: 7.8.1 - tinyglobby: 0.2.16 + tinyglobby: 0.2.17 ts-api-utils: 2.5.0(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: @@ -29731,6 +29857,33 @@ snapshots: transitivePeerDependencies: - supports-color + '@uiw/codemirror-extensions-basic-setup@4.25.11(@codemirror/autocomplete@6.20.3)(@codemirror/commands@6.10.4)(@codemirror/language@6.12.4)(@codemirror/lint@6.9.7)(@codemirror/search@6.7.1)(@codemirror/state@6.7.1)(@codemirror/view@6.43.6)': + dependencies: + '@codemirror/autocomplete': 6.20.3 + '@codemirror/commands': 6.10.4 + '@codemirror/language': 6.12.4 + '@codemirror/lint': 6.9.7 + '@codemirror/search': 6.7.1 + '@codemirror/state': 6.7.1 + '@codemirror/view': 6.43.6 + + '@uiw/react-codemirror@4.25.11(@babel/runtime@7.29.7)(@codemirror/autocomplete@6.20.3)(@codemirror/language@6.12.4)(@codemirror/lint@6.9.7)(@codemirror/search@6.7.1)(@codemirror/state@6.7.1)(@codemirror/theme-one-dark@6.1.3)(@codemirror/view@6.43.6)(codemirror@6.0.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@babel/runtime': 7.29.7 + '@codemirror/commands': 6.10.4 + '@codemirror/state': 6.7.1 + '@codemirror/theme-one-dark': 6.1.3 + '@codemirror/view': 6.43.6 + '@uiw/codemirror-extensions-basic-setup': 4.25.11(@codemirror/autocomplete@6.20.3)(@codemirror/commands@6.10.4)(@codemirror/language@6.12.4)(@codemirror/lint@6.9.7)(@codemirror/search@6.7.1)(@codemirror/state@6.7.1)(@codemirror/view@6.43.6) + codemirror: 6.0.2 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + transitivePeerDependencies: + - '@codemirror/autocomplete' + - '@codemirror/language' + - '@codemirror/lint' + - '@codemirror/search' + '@ungap/structured-clone@1.3.1': {} '@unhead/vue@2.1.15(vue@3.5.34(typescript@5.9.3))': @@ -30096,7 +30249,7 @@ snapshots: '@vue/shared': 3.5.31 estree-walker: 2.0.2 magic-string: 0.30.21 - postcss: 8.5.15 + postcss: 8.5.16 source-map-js: 1.2.1 '@vue/compiler-sfc@3.5.34': @@ -30672,15 +30825,6 @@ snapshots: atomic-sleep@1.0.0: {} - autoprefixer@10.5.0(postcss@8.5.15): - dependencies: - browserslist: 4.28.2 - caniuse-lite: 1.0.30001793 - fraction.js: 5.3.4 - picocolors: 1.1.1 - postcss: 8.5.15 - postcss-value-parser: 4.2.0 - autoprefixer@10.5.0(postcss@8.5.16): dependencies: browserslist: 4.28.2 @@ -31299,6 +31443,16 @@ snapshots: code-block-writer@13.0.3: {} + codemirror@6.0.2: + dependencies: + '@codemirror/autocomplete': 6.20.3 + '@codemirror/commands': 6.10.4 + '@codemirror/language': 6.12.4 + '@codemirror/lint': 6.9.7 + '@codemirror/search': 6.7.1 + '@codemirror/state': 6.7.1 + '@codemirror/view': 6.43.6 + collapse-white-space@2.1.0: {} color-convert@1.9.3: @@ -31534,9 +31688,9 @@ snapshots: optionalDependencies: srvx: 0.11.16 - css-declaration-sorter@7.3.1(postcss@8.5.15): + css-declaration-sorter@7.3.1(postcss@8.5.16): dependencies: - postcss: 8.5.15 + postcss: 8.5.16 css-in-js-utils@3.1.0: dependencies: @@ -31571,49 +31725,49 @@ snapshots: cssesc@3.0.0: {} - cssnano-preset-default@7.0.17(postcss@8.5.15): + cssnano-preset-default@7.0.17(postcss@8.5.16): dependencies: browserslist: 4.28.2 - css-declaration-sorter: 7.3.1(postcss@8.5.15) - cssnano-utils: 5.0.3(postcss@8.5.15) - postcss: 8.5.15 - postcss-calc: 10.1.1(postcss@8.5.15) - postcss-colormin: 7.0.10(postcss@8.5.15) - postcss-convert-values: 7.0.12(postcss@8.5.15) - postcss-discard-comments: 7.0.8(postcss@8.5.15) - postcss-discard-duplicates: 7.0.4(postcss@8.5.15) - postcss-discard-empty: 7.0.3(postcss@8.5.15) - postcss-discard-overridden: 7.0.3(postcss@8.5.15) - postcss-merge-longhand: 7.0.7(postcss@8.5.15) - postcss-merge-rules: 7.0.11(postcss@8.5.15) - postcss-minify-font-values: 7.0.3(postcss@8.5.15) - postcss-minify-gradients: 7.0.5(postcss@8.5.15) - postcss-minify-params: 7.0.9(postcss@8.5.15) - postcss-minify-selectors: 7.1.2(postcss@8.5.15) - postcss-normalize-charset: 7.0.3(postcss@8.5.15) - postcss-normalize-display-values: 7.0.3(postcss@8.5.15) - postcss-normalize-positions: 7.0.4(postcss@8.5.15) - postcss-normalize-repeat-style: 7.0.4(postcss@8.5.15) - postcss-normalize-string: 7.0.3(postcss@8.5.15) - postcss-normalize-timing-functions: 7.0.3(postcss@8.5.15) - postcss-normalize-unicode: 7.0.9(postcss@8.5.15) - postcss-normalize-url: 7.0.3(postcss@8.5.15) - postcss-normalize-whitespace: 7.0.3(postcss@8.5.15) - postcss-ordered-values: 7.0.4(postcss@8.5.15) - postcss-reduce-initial: 7.0.9(postcss@8.5.15) - postcss-reduce-transforms: 7.0.3(postcss@8.5.15) - postcss-svgo: 7.1.3(postcss@8.5.15) - postcss-unique-selectors: 7.0.7(postcss@8.5.15) - - cssnano-utils@5.0.3(postcss@8.5.15): - dependencies: - postcss: 8.5.15 - - cssnano@7.1.9(postcss@8.5.15): - dependencies: - cssnano-preset-default: 7.0.17(postcss@8.5.15) + css-declaration-sorter: 7.3.1(postcss@8.5.16) + cssnano-utils: 5.0.3(postcss@8.5.16) + postcss: 8.5.16 + postcss-calc: 10.1.1(postcss@8.5.16) + postcss-colormin: 7.0.10(postcss@8.5.16) + postcss-convert-values: 7.0.12(postcss@8.5.16) + postcss-discard-comments: 7.0.8(postcss@8.5.16) + postcss-discard-duplicates: 7.0.4(postcss@8.5.16) + postcss-discard-empty: 7.0.3(postcss@8.5.16) + postcss-discard-overridden: 7.0.3(postcss@8.5.16) + postcss-merge-longhand: 7.0.7(postcss@8.5.16) + postcss-merge-rules: 7.0.11(postcss@8.5.16) + postcss-minify-font-values: 7.0.3(postcss@8.5.16) + postcss-minify-gradients: 7.0.5(postcss@8.5.16) + postcss-minify-params: 7.0.9(postcss@8.5.16) + postcss-minify-selectors: 7.1.2(postcss@8.5.16) + postcss-normalize-charset: 7.0.3(postcss@8.5.16) + postcss-normalize-display-values: 7.0.3(postcss@8.5.16) + postcss-normalize-positions: 7.0.4(postcss@8.5.16) + postcss-normalize-repeat-style: 7.0.4(postcss@8.5.16) + postcss-normalize-string: 7.0.3(postcss@8.5.16) + postcss-normalize-timing-functions: 7.0.3(postcss@8.5.16) + postcss-normalize-unicode: 7.0.9(postcss@8.5.16) + postcss-normalize-url: 7.0.3(postcss@8.5.16) + postcss-normalize-whitespace: 7.0.3(postcss@8.5.16) + postcss-ordered-values: 7.0.4(postcss@8.5.16) + postcss-reduce-initial: 7.0.9(postcss@8.5.16) + postcss-reduce-transforms: 7.0.3(postcss@8.5.16) + postcss-svgo: 7.1.3(postcss@8.5.16) + postcss-unique-selectors: 7.0.7(postcss@8.5.16) + + cssnano-utils@5.0.3(postcss@8.5.16): + dependencies: + postcss: 8.5.16 + + cssnano@7.1.9(postcss@8.5.16): + dependencies: + cssnano-preset-default: 7.0.17(postcss@8.5.16) lilconfig: 3.1.3 - postcss: 8.5.15 + postcss: 8.5.16 csso@5.0.5: dependencies: @@ -32740,7 +32894,7 @@ snapshots: estree-walker@3.0.3: dependencies: - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 esutils@2.0.3: {} @@ -33544,8 +33698,6 @@ snapshots: get-caller-file@2.0.5: {} - get-east-asian-width@1.5.0: {} - get-east-asian-width@1.6.0: {} get-intrinsic@1.3.0: @@ -33591,10 +33743,6 @@ snapshots: es-errors: 1.3.0 get-intrinsic: 1.3.0 - get-tsconfig@4.10.1: - dependencies: - resolve-pkg-maps: 1.0.0 - get-tsconfig@4.14.0: dependencies: resolve-pkg-maps: 1.0.0 @@ -33947,9 +34095,9 @@ snapshots: mdast-util-mdxjs-esm: 2.0.1 property-information: 7.1.0 space-separated-tokens: 2.0.2 - style-to-js: 1.1.17 + style-to-js: 1.1.21 unist-util-position: 5.0.0 - vfile-message: 4.0.2 + vfile-message: 4.0.3 transitivePeerDependencies: - supports-color @@ -34212,8 +34360,6 @@ snapshots: ini@4.1.1: {} - inline-style-parser@0.2.4: {} - inline-style-parser@0.2.7: {} inline-style-prefixer@7.0.1: @@ -34311,7 +34457,7 @@ snapshots: is-bun-module@2.0.0: dependencies: - semver: 7.8.1 + semver: 7.8.5 is-callable@1.2.7: {} @@ -34439,7 +34585,7 @@ snapshots: is-typed-array@1.1.15: dependencies: - which-typed-array: 1.1.19 + which-typed-array: 1.1.20 is-unicode-supported@2.1.0: {} @@ -34742,7 +34888,7 @@ snapshots: lodash.isstring: 4.0.1 lodash.once: 4.1.1 ms: 2.1.3 - semver: 7.8.1 + semver: 7.8.5 jsx-ast-utils@3.3.5: dependencies: @@ -35328,23 +35474,6 @@ snapshots: unist-util-is: 6.0.0 unist-util-visit-parents: 6.0.1 - mdast-util-from-markdown@2.0.2: - dependencies: - '@types/mdast': 4.0.4 - '@types/unist': 3.0.3 - decode-named-character-reference: 1.2.0 - devlop: 1.1.0 - mdast-util-to-string: 4.0.0 - micromark: 4.0.2 - micromark-util-decode-numeric-character-reference: 2.0.2 - micromark-util-decode-string: 2.0.1 - micromark-util-normalize-identifier: 2.0.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - unist-util-stringify-position: 4.0.0 - transitivePeerDependencies: - - supports-color - mdast-util-from-markdown@2.0.3: dependencies: '@types/mdast': 4.0.4 @@ -35437,7 +35566,7 @@ snapshots: '@types/hast': 3.0.4 '@types/mdast': 4.0.4 devlop: 1.1.0 - mdast-util-from-markdown: 2.0.2 + mdast-util-from-markdown: 2.0.3 mdast-util-to-markdown: 2.1.2 transitivePeerDependencies: - supports-color @@ -35450,12 +35579,12 @@ snapshots: '@types/unist': 3.0.3 ccount: 2.0.1 devlop: 1.1.0 - mdast-util-from-markdown: 2.0.2 + mdast-util-from-markdown: 2.0.3 mdast-util-to-markdown: 2.1.2 parse-entities: 4.0.2 stringify-entities: 4.0.4 unist-util-stringify-position: 4.0.0 - vfile-message: 4.0.2 + vfile-message: 4.0.3 transitivePeerDependencies: - supports-color @@ -35475,7 +35604,7 @@ snapshots: '@types/hast': 3.0.4 '@types/mdast': 4.0.4 devlop: 1.1.0 - mdast-util-from-markdown: 2.0.2 + mdast-util-from-markdown: 2.0.3 mdast-util-to-markdown: 2.1.2 transitivePeerDependencies: - supports-color @@ -36179,7 +36308,7 @@ snapshots: dependencies: '@types/debug': 4.1.12 debug: 4.4.3 - decode-named-character-reference: 1.2.0 + decode-named-character-reference: 1.3.0 devlop: 1.1.0 micromark-core-commonmark: 2.0.3 micromark-factory-space: 2.0.1 @@ -36437,7 +36566,7 @@ snapshots: '@next/env': 15.5.18 '@swc/helpers': 0.5.15 caniuse-lite: 1.0.30001793 - postcss: 8.5.15 + postcss: 8.5.16 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) styled-jsx: 5.1.6(@babel/core@7.29.7)(react@19.2.4) @@ -36545,7 +36674,7 @@ snapshots: '@swc/helpers': 0.5.15 baseline-browser-mapping: 2.10.32 caniuse-lite: 1.0.30001793 - postcss: 8.5.15 + postcss: 8.5.16 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) styled-jsx: 5.1.6(@babel/core@7.29.7)(babel-plugin-macros@3.1.0)(react@19.2.3) @@ -36728,7 +36857,7 @@ snapshots: node-abi@3.94.0: dependencies: - semver: 7.8.1 + semver: 7.8.5 node-addon-api@7.1.1: {} @@ -36775,7 +36904,7 @@ snapshots: nopt: 5.0.0 npmlog: 6.0.2 rimraf: 3.0.2 - semver: 7.8.1 + semver: 7.8.5 tar: 6.2.1 which: 2.0.2 transitivePeerDependencies: @@ -37586,101 +37715,101 @@ snapshots: possible-typed-array-names@1.1.0: {} - postcss-calc@10.1.1(postcss@8.5.15): + postcss-calc@10.1.1(postcss@8.5.16): dependencies: - postcss: 8.5.15 + postcss: 8.5.16 postcss-selector-parser: 7.1.1 postcss-value-parser: 4.2.0 - postcss-colormin@7.0.10(postcss@8.5.15): + postcss-colormin@7.0.10(postcss@8.5.16): dependencies: '@colordx/core': 5.4.3 browserslist: 4.28.2 caniuse-api: 3.0.0 - postcss: 8.5.15 + postcss: 8.5.16 postcss-value-parser: 4.2.0 - postcss-convert-values@7.0.12(postcss@8.5.15): + postcss-convert-values@7.0.12(postcss@8.5.16): dependencies: browserslist: 4.28.2 - postcss: 8.5.15 + postcss: 8.5.16 postcss-value-parser: 4.2.0 - postcss-discard-comments@7.0.8(postcss@8.5.15): + postcss-discard-comments@7.0.8(postcss@8.5.16): dependencies: - postcss: 8.5.15 + postcss: 8.5.16 postcss-selector-parser: 7.1.1 - postcss-discard-duplicates@7.0.4(postcss@8.5.15): + postcss-discard-duplicates@7.0.4(postcss@8.5.16): dependencies: - postcss: 8.5.15 + postcss: 8.5.16 - postcss-discard-empty@7.0.3(postcss@8.5.15): + postcss-discard-empty@7.0.3(postcss@8.5.16): dependencies: - postcss: 8.5.15 + postcss: 8.5.16 - postcss-discard-overridden@7.0.3(postcss@8.5.15): + postcss-discard-overridden@7.0.3(postcss@8.5.16): dependencies: - postcss: 8.5.15 + postcss: 8.5.16 - postcss-import@15.1.0(postcss@8.5.15): + postcss-import@15.1.0(postcss@8.5.16): dependencies: - postcss: 8.5.15 + postcss: 8.5.16 postcss-value-parser: 4.2.0 read-cache: 1.0.0 resolve: 1.22.11 - postcss-js@4.0.1(postcss@8.5.15): + postcss-js@4.0.1(postcss@8.5.16): dependencies: camelcase-css: 2.0.1 - postcss: 8.5.15 + postcss: 8.5.16 - postcss-load-config@4.0.2(postcss@8.5.15): + postcss-load-config@4.0.2(postcss@8.5.16): dependencies: lilconfig: 3.1.3 yaml: 2.9.0 optionalDependencies: - postcss: 8.5.15 + postcss: 8.5.16 - postcss-merge-longhand@7.0.7(postcss@8.5.15): + postcss-merge-longhand@7.0.7(postcss@8.5.16): dependencies: - postcss: 8.5.15 + postcss: 8.5.16 postcss-value-parser: 4.2.0 - stylehacks: 7.0.11(postcss@8.5.15) + stylehacks: 7.0.11(postcss@8.5.16) - postcss-merge-rules@7.0.11(postcss@8.5.15): + postcss-merge-rules@7.0.11(postcss@8.5.16): dependencies: browserslist: 4.28.2 caniuse-api: 3.0.0 - cssnano-utils: 5.0.3(postcss@8.5.15) - postcss: 8.5.15 + cssnano-utils: 5.0.3(postcss@8.5.16) + postcss: 8.5.16 postcss-selector-parser: 7.1.1 - postcss-minify-font-values@7.0.3(postcss@8.5.15): + postcss-minify-font-values@7.0.3(postcss@8.5.16): dependencies: - postcss: 8.5.15 + postcss: 8.5.16 postcss-value-parser: 4.2.0 - postcss-minify-gradients@7.0.5(postcss@8.5.15): + postcss-minify-gradients@7.0.5(postcss@8.5.16): dependencies: '@colordx/core': 5.4.3 - cssnano-utils: 5.0.3(postcss@8.5.15) - postcss: 8.5.15 + cssnano-utils: 5.0.3(postcss@8.5.16) + postcss: 8.5.16 postcss-value-parser: 4.2.0 - postcss-minify-params@7.0.9(postcss@8.5.15): + postcss-minify-params@7.0.9(postcss@8.5.16): dependencies: browserslist: 4.28.2 - cssnano-utils: 5.0.3(postcss@8.5.15) - postcss: 8.5.15 + cssnano-utils: 5.0.3(postcss@8.5.16) + postcss: 8.5.16 postcss-value-parser: 4.2.0 - postcss-minify-selectors@7.1.2(postcss@8.5.15): + postcss-minify-selectors@7.1.2(postcss@8.5.16): dependencies: browserslist: 4.28.2 caniuse-api: 3.0.0 cssesc: 3.0.0 - postcss: 8.5.15 + postcss: 8.5.16 postcss-selector-parser: 7.1.1 postcss-modules-extract-imports@3.1.0(postcss@8.5.16): @@ -37716,76 +37845,76 @@ snapshots: postcss-modules-values: 4.0.0(postcss@8.5.16) string-hash: 1.1.3 - postcss-nested@6.2.0(postcss@8.5.15): + postcss-nested@6.2.0(postcss@8.5.16): dependencies: - postcss: 8.5.15 + postcss: 8.5.16 postcss-selector-parser: 6.1.2 - postcss-normalize-charset@7.0.3(postcss@8.5.15): + postcss-normalize-charset@7.0.3(postcss@8.5.16): dependencies: - postcss: 8.5.15 + postcss: 8.5.16 - postcss-normalize-display-values@7.0.3(postcss@8.5.15): + postcss-normalize-display-values@7.0.3(postcss@8.5.16): dependencies: - postcss: 8.5.15 + postcss: 8.5.16 postcss-value-parser: 4.2.0 - postcss-normalize-positions@7.0.4(postcss@8.5.15): + postcss-normalize-positions@7.0.4(postcss@8.5.16): dependencies: - postcss: 8.5.15 + postcss: 8.5.16 postcss-value-parser: 4.2.0 - postcss-normalize-repeat-style@7.0.4(postcss@8.5.15): + postcss-normalize-repeat-style@7.0.4(postcss@8.5.16): dependencies: - postcss: 8.5.15 + postcss: 8.5.16 postcss-value-parser: 4.2.0 - postcss-normalize-string@7.0.3(postcss@8.5.15): + postcss-normalize-string@7.0.3(postcss@8.5.16): dependencies: - postcss: 8.5.15 + postcss: 8.5.16 postcss-value-parser: 4.2.0 - postcss-normalize-timing-functions@7.0.3(postcss@8.5.15): + postcss-normalize-timing-functions@7.0.3(postcss@8.5.16): dependencies: - postcss: 8.5.15 + postcss: 8.5.16 postcss-value-parser: 4.2.0 - postcss-normalize-unicode@7.0.9(postcss@8.5.15): + postcss-normalize-unicode@7.0.9(postcss@8.5.16): dependencies: browserslist: 4.28.2 - postcss: 8.5.15 + postcss: 8.5.16 postcss-value-parser: 4.2.0 - postcss-normalize-url@7.0.3(postcss@8.5.15): + postcss-normalize-url@7.0.3(postcss@8.5.16): dependencies: - postcss: 8.5.15 + postcss: 8.5.16 postcss-value-parser: 4.2.0 - postcss-normalize-whitespace@7.0.3(postcss@8.5.15): + postcss-normalize-whitespace@7.0.3(postcss@8.5.16): dependencies: - postcss: 8.5.15 + postcss: 8.5.16 postcss-value-parser: 4.2.0 - postcss-ordered-values@7.0.4(postcss@8.5.15): + postcss-ordered-values@7.0.4(postcss@8.5.16): dependencies: - cssnano-utils: 5.0.3(postcss@8.5.15) - postcss: 8.5.15 + cssnano-utils: 5.0.3(postcss@8.5.16) + postcss: 8.5.16 postcss-value-parser: 4.2.0 - postcss-reduce-initial@7.0.9(postcss@8.5.15): + postcss-reduce-initial@7.0.9(postcss@8.5.16): dependencies: browserslist: 4.28.2 caniuse-api: 3.0.0 - postcss: 8.5.15 + postcss: 8.5.16 - postcss-reduce-transforms@7.0.3(postcss@8.5.15): + postcss-reduce-transforms@7.0.3(postcss@8.5.16): dependencies: - postcss: 8.5.15 + postcss: 8.5.16 postcss-value-parser: 4.2.0 - postcss-remove-duplicate-values@1.0.0(postcss@8.5.15): + postcss-remove-duplicate-values@1.0.0(postcss@8.5.16): dependencies: - postcss: 8.5.15 + postcss: 8.5.16 postcss-selector-parser@6.1.2: dependencies: @@ -37797,25 +37926,19 @@ snapshots: cssesc: 3.0.0 util-deprecate: 1.0.2 - postcss-svgo@7.1.3(postcss@8.5.15): + postcss-svgo@7.1.3(postcss@8.5.16): dependencies: - postcss: 8.5.15 + postcss: 8.5.16 postcss-value-parser: 4.2.0 svgo: 4.0.1 - postcss-unique-selectors@7.0.7(postcss@8.5.15): + postcss-unique-selectors@7.0.7(postcss@8.5.16): dependencies: - postcss: 8.5.15 + postcss: 8.5.16 postcss-selector-parser: 7.1.1 postcss-value-parser@4.2.0: {} - postcss@8.5.15: - dependencies: - nanoid: 3.3.12 - picocolors: 1.1.1 - source-map-js: 1.2.1 - postcss@8.5.16: dependencies: nanoid: 3.3.12 @@ -38482,7 +38605,7 @@ snapshots: react-refresh: 0.14.2 regenerator-runtime: 0.13.11 scheduler: 0.27.0 - semver: 7.7.4 + semver: 7.8.1 stacktrace-parser: 0.1.11 whatwg-fetch: 3.6.20 ws: 7.5.11 @@ -38565,6 +38688,11 @@ snapshots: optionalDependencies: '@types/react': 19.2.14 + react-resizable-panels@4.12.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + dependencies: + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + react-smooth@4.0.4(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: fast-equals: 5.2.2 @@ -38933,7 +39061,7 @@ snapshots: remark-parse@11.0.0: dependencies: '@types/mdast': 4.0.4 - mdast-util-from-markdown: 2.0.2 + mdast-util-from-markdown: 2.0.3 micromark-util-types: 2.0.2 unified: 11.0.5 transitivePeerDependencies: @@ -39268,12 +39396,12 @@ snapshots: semver@6.3.1: {} - semver@7.7.4: {} - semver@7.8.0: {} semver@7.8.1: {} + semver@7.8.5: {} + send@0.19.2: dependencies: debug: 2.6.9 @@ -39371,7 +39499,7 @@ snapshots: dependencies: '@img/colour': 1.0.0 detect-libc: 2.1.2 - semver: 7.7.4 + semver: 7.8.5 optionalDependencies: '@img/sharp-darwin-arm64': 0.34.5 '@img/sharp-darwin-x64': 0.34.5 @@ -39677,7 +39805,7 @@ snapshots: string-width@7.2.0: dependencies: emoji-regex: 10.6.0 - get-east-asian-width: 1.5.0 + get-east-asian-width: 1.6.0 strip-ansi: 7.1.0 string.prototype.includes@2.0.1: @@ -39791,9 +39919,7 @@ snapshots: stubs@3.0.0: {} - style-to-js@1.1.17: - dependencies: - style-to-object: 1.0.9 + style-mod@4.1.3: {} style-to-js@1.1.21: dependencies: @@ -39803,10 +39929,6 @@ snapshots: dependencies: inline-style-parser: 0.2.7 - style-to-object@1.0.9: - dependencies: - inline-style-parser: 0.2.4 - styled-jsx@5.1.6(@babel/core@7.29.7)(babel-plugin-macros@3.1.0)(react@19.2.3): dependencies: client-only: 0.0.1 @@ -39822,10 +39944,10 @@ snapshots: optionalDependencies: '@babel/core': 7.29.7 - stylehacks@7.0.11(postcss@8.5.15): + stylehacks@7.0.11(postcss@8.5.16): dependencies: browserslist: 4.28.2 - postcss: 8.5.15 + postcss: 8.5.16 postcss-selector-parser: 7.1.1 styleq@0.1.3: {} @@ -39970,11 +40092,11 @@ snapshots: normalize-path: 3.0.0 object-hash: 3.0.0 picocolors: 1.1.1 - postcss: 8.5.15 - postcss-import: 15.1.0(postcss@8.5.15) - postcss-js: 4.0.1(postcss@8.5.15) - postcss-load-config: 4.0.2(postcss@8.5.15) - postcss-nested: 6.2.0(postcss@8.5.15) + postcss: 8.5.16 + postcss-import: 15.1.0(postcss@8.5.16) + postcss-js: 4.0.1(postcss@8.5.16) + postcss-load-config: 4.0.2(postcss@8.5.16) + postcss-nested: 6.2.0(postcss@8.5.16) postcss-selector-parser: 6.1.2 resolve: 1.22.11 sucrase: 3.35.1 @@ -40070,17 +40192,17 @@ snapshots: ansi-escapes: 4.3.2 supports-hyperlinks: 2.3.0 - terser-webpack-plugin@5.6.0(esbuild@0.25.12)(lightningcss@1.32.0)(postcss@8.5.15)(webpack@5.107.2(esbuild@0.25.12)(lightningcss@1.32.0)(postcss@8.5.15)): + terser-webpack-plugin@5.6.0(esbuild@0.25.12)(lightningcss@1.32.0)(postcss@8.5.16)(webpack@5.107.2(esbuild@0.25.12)(lightningcss@1.32.0)(postcss@8.5.16)): dependencies: '@jridgewell/trace-mapping': 0.3.31 jest-worker: 27.5.1 schema-utils: 4.3.3 terser: 5.48.0 - webpack: 5.107.2(esbuild@0.25.12)(lightningcss@1.32.0)(postcss@8.5.15) + webpack: 5.107.2(esbuild@0.25.12)(lightningcss@1.32.0)(postcss@8.5.16) optionalDependencies: esbuild: 0.25.12 lightningcss: 1.32.0 - postcss: 8.5.15 + postcss: 8.5.16 terser@5.48.0: dependencies: @@ -40271,7 +40393,7 @@ snapshots: tsx@4.20.3: dependencies: esbuild: 0.25.12 - get-tsconfig: 4.10.1 + get-tsconfig: 4.14.0 optionalDependencies: fsevents: 2.3.3 @@ -40305,7 +40427,7 @@ snapshots: graphql: 16.14.0 graphql-query-complexity: 0.12.0(graphql@16.14.0) graphql-scalars: 1.25.0(graphql@16.14.0) - semver: 7.8.1 + semver: 7.8.5 tslib: 2.8.1 optionalDependencies: class-validator: 0.14.4 @@ -40749,11 +40871,6 @@ snapshots: '@types/unist': 3.0.3 vfile: 6.0.3 - vfile-message@4.0.2: - dependencies: - '@types/unist': 3.0.3 - unist-util-stringify-position: 4.0.0 - vfile-message@4.0.3: dependencies: '@types/unist': 3.0.3 @@ -40762,7 +40879,7 @@ snapshots: vfile@6.0.3: dependencies: '@types/unist': 3.0.3 - vfile-message: 4.0.2 + vfile-message: 4.0.3 victory-vendor@36.9.2: dependencies: @@ -40861,7 +40978,7 @@ snapshots: esbuild: 0.25.12 fdir: 6.5.0(picomatch@4.0.4) picomatch: 4.0.4 - postcss: 8.5.15 + postcss: 8.5.16 rollup: 4.60.4 tinyglobby: 0.2.16 optionalDependencies: @@ -40879,7 +40996,7 @@ snapshots: esbuild: 0.25.12 fdir: 6.5.0(picomatch@4.0.4) picomatch: 4.0.4 - postcss: 8.5.15 + postcss: 8.5.16 rollup: 4.60.4 tinyglobby: 0.2.16 optionalDependencies: @@ -41212,7 +41329,7 @@ snapshots: webpack-virtual-modules@0.6.2: {} - webpack@5.107.2(esbuild@0.25.12)(lightningcss@1.32.0)(postcss@8.5.15): + webpack@5.107.2(esbuild@0.25.12)(lightningcss@1.32.0)(postcss@8.5.16): dependencies: '@types/estree': 1.0.9 '@types/json-schema': 7.0.15 @@ -41234,7 +41351,7 @@ snapshots: neo-async: 2.6.2 schema-utils: 4.3.3 tapable: 2.3.3 - terser-webpack-plugin: 5.6.0(esbuild@0.25.12)(lightningcss@1.32.0)(postcss@8.5.15)(webpack@5.107.2(esbuild@0.25.12)(lightningcss@1.32.0)(postcss@8.5.15)) + terser-webpack-plugin: 5.6.0(esbuild@0.25.12)(lightningcss@1.32.0)(postcss@8.5.16)(webpack@5.107.2(esbuild@0.25.12)(lightningcss@1.32.0)(postcss@8.5.16)) watchpack: 2.5.1 webpack-sources: 3.5.0 transitivePeerDependencies: @@ -41318,16 +41435,6 @@ snapshots: is-weakmap: 2.0.2 is-weakset: 2.0.4 - which-typed-array@1.1.19: - dependencies: - available-typed-arrays: 1.0.7 - call-bind: 1.0.9 - call-bound: 1.0.4 - for-each: 0.3.5 - get-proto: 1.0.1 - gopd: 1.2.0 - has-tostringtag: 1.0.2 - which-typed-array@1.1.20: dependencies: available-typed-arrays: 1.0.7