diff --git a/.dockerignore b/.dockerignore index 77d03df..61ef91a 100644 --- a/.dockerignore +++ b/.dockerignore @@ -17,11 +17,21 @@ !packages/ !packages/client/ !packages/client/package.json +!packages/client/tsconfig.json !packages/client/src/ !packages/client/src/** !examples/ !examples/collaboration/ !examples/collaboration/package.json -!examples/collaboration/*.html -!examples/collaboration/*.js -!examples/collaboration/*.css +!examples/collaboration/**/ +!examples/collaboration/**/*.ts +!examples/collaboration/**/*.tsx +!examples/collaboration/**/*.css +!examples/collaboration/components.json +!examples/collaboration/tsconfig.json +examples/collaboration/node_modules/ +examples/collaboration/.next/ +examples/collaboration/out/ +examples/collaboration/test-results/ + +examples/collaboration/next-env.d.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 63879be..5023133 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -92,12 +92,15 @@ jobs: with: node-version-file: examples/collaboration/.nvmrc - - name: Install JavaScript workspace dependencies + - name: Install TypeScript workspace dependencies run: npm ci - name: Test the client SDK run: npm test + - name: Typecheck the SDK, examples, tests, and tools + run: npm run typecheck + - name: Verify the packed client SDK run: npm run test:package diff --git a/.gitignore b/.gitignore index dcddb70..9b31f99 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,10 @@ node_modules/ # Local staging credentials, certificates, backups and reports. /.local/ __pycache__/ + +# TypeScript and Next.js build output. +/packages/client/dist/ +/examples/collaboration/.next/ +/examples/collaboration/out/ +*.tsbuildinfo +/examples/collaboration/next-env.d.ts diff --git a/Dockerfile b/Dockerfile index 52bb494..b282701 100644 --- a/Dockerfile +++ b/Dockerfile @@ -19,7 +19,7 @@ COPY config/config.exs config/prod.exs config/ RUN mix deps.get --only prod --check-locked && mix deps.compile COPY lib lib COPY priv priv -COPY --from=client /src/examples/collaboration/dist/ priv/static/ +COPY --from=client /src/examples/collaboration/out/ priv/static/ RUN mix compile --warnings-as-errors COPY config/runtime.exs config/ RUN mix release diff --git a/README.md b/README.md index 3b23156..e019fc6 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ browser pilot, certificate renewal, backups, and upgrades. ## Local setup Use this setup to develop the Phoenix backend and run the browser examples with -Vite. It uses a separate database and ports from Docker staging. +Next.js. It uses a separate database and ports from Docker staging. - Elixir 1.18.3 and Erlang/OTP 27.3.3, pinned in `.tool-versions` for asdf. - A C compiler and `make` for Argon2 password hashing, such as the Xcode Command @@ -99,7 +99,7 @@ versions in `.tool-versions`. Run the commands below from the project directory. ``` The server listens at [localhost:4000](http://localhost:4000), with a collaboration -WebSocket at `/socket/websocket`. Open the Vite example below for the development +WebSocket at `/socket/websocket`. Open the Next.js example below for the development UI; the Docker release serves the packaged example at `/`. Use `iex -S mix phx.server` for an interactive shell. @@ -115,6 +115,11 @@ The configured role must be able to create databases. ## Try the collaboration example +The examples use Next.js App Router, React, TypeScript, shadcn/ui, and Tailwind CSS. +Elixir/Phoenix remains the backend for accounts, permissions, persistence, and +WebSockets. The production build exports static pages for Phoenix to serve; no +Node server is needed in the release. See the [frontend guide](examples/collaboration/README.md). + The browser example uses Node.js 24.12.0, pinned in `examples/collaboration/.nvmrc`. Complete local setup above first. Restart Phoenix after updating the application's supervision tree or configuration. @@ -200,7 +205,7 @@ The production build also includes `/kanban.html`. - Offline changes remain in the open tab. Reconnect and wait for **Saved** before closing it. Saved boards recover through the existing PostgreSQL document log. -The [board model](examples/collaboration/kanban/model.js) stores each card as a +The [board model](examples/collaboration/kanban/model.ts) stores each card as a nested Y.Map under `kanban:cards:v1`. Column and order are a single placement value, so simultaneous moves converge to one location without duplicating a card. Edits to different fields merge. Concurrent edits to the same title, description, @@ -209,7 +214,7 @@ fields do not provide character-level text merging. A deletion wins over a concurrent edit to that card. Ordering ties use the card ID. This first example has fixed columns and does not support reordering within a column. -The [shared example shell](examples/collaboration/example-shell.js) owns account, +The [shared React application](examples/collaboration/components/example-app.tsx) owns account, room, connection, permission, and cleanup UI for all examples. Each editing surface consumes the public SDK. Kanban's data uses a separate shared type, so opening a text room as a board does not change its text content. @@ -221,7 +226,7 @@ recovery, server restart, permissions, room navigation, and mobile layout. Run only the Kanban browser checks with: ```sh -npm test --workspace synixir-collaboration-example -- kanban.spec.js +npm test --workspace synixir-collaboration-example -- kanban.spec.ts ``` ## Try the whiteboard example @@ -251,7 +256,7 @@ Each example stores its data separately inside that room's Yjs document. - Undo and redo affect this tab's changes. Viewers can select objects and publish cursors, but cannot change content, geometry, or layer order. -The [whiteboard model](examples/collaboration/whiteboard/model.js) uses nested +The [whiteboard model](examples/collaboration/whiteboard/model.ts) uses nested Y.Maps under `whiteboard:objects:v1`. Position and size are separate atomic values, so moving an object does not overwrite a teammate's text or color edits. Simultaneous edits to the same property resolve to one value through Y.Map's @@ -266,7 +271,7 @@ room permissions, PostgreSQL persistence, and recovery flow as the other example checks separately from `mix test` with: ```sh -npm test --workspace synixir-collaboration-example -- whiteboard.spec.js +npm test --workspace synixir-collaboration-example -- whiteboard.spec.ts ``` ## Try the rich-text example @@ -286,7 +291,7 @@ one. The plain-text editor remains available as its own example. Wait for **Saved** before closing the tab. Saved formatting and content recover after everyone leaves or the server restarts. -The [rich-text editor](examples/collaboration/rich-text/editor.js) uses Tiptap with +The [rich-text editor](examples/collaboration/rich-text/editor.ts) uses Tiptap with its [Yjs collaboration extension](https://tiptap.dev/docs/editor/extensions/functionality/collaboration). It binds to the `rich-text:content:v1` Y.XmlFragment in the SDK's document and uses Synixir for synchronization and persistence. It does not require a separate @@ -301,7 +306,7 @@ storage quotas apply to the whole room. Run its browser checks separately from `mix test` with: ```sh -npm test --workspace synixir-collaboration-example -- rich-text.spec.js +npm test --workspace synixir-collaboration-example -- rich-text.spec.ts ``` ## Try the multiplayer form example @@ -326,7 +331,7 @@ then open the form in another tab to collaborate on a project brief. - Undo and redo affect this tab's edits across text and choice fields. Viewers can read, select text, and review the draft; they cannot edit or undo it. -The [form model](examples/collaboration/multiplayer-form/model.js) stores text in +The [form model](examples/collaboration/multiplayer-form/model.ts) stores text in separate top-level Y.Text fields named `multiplayer-form::v1`, with choices in `multiplayer-form:properties:v1`. The text controls reuse the existing CodeMirror/Yjs binding for shared selections, composition input, and undo. @@ -340,7 +345,7 @@ before closing it. Saved fields recover after all tabs close or the server resta separately from `mix test` with: ```sh -npm test --workspace synixir-collaboration-example -- multiplayer-form.spec.js +npm test --workspace synixir-collaboration-example -- multiplayer-form.spec.ts ``` ## Try the flowchart builder @@ -361,7 +366,7 @@ another tab to build a workflow together. - Undo and redo affect this tab's changes. Viewers can explore and select nodes and connections, while edits and history controls require editing permission. -The [flowchart model](examples/collaboration/flowchart/model.js) stores nested +The [flowchart model](examples/collaboration/flowchart/model.ts) stores nested Y.Maps under `flowchart:nodes:v1` and `flowchart:edges:v1`. Fields merge independently; concurrent edits to the same field resolve to one value using Y.Map's conflict rules. Connections use a key derived from their source and target so concurrent @@ -383,7 +388,7 @@ before closing it. Saved nodes and arrows recover after a server restart. checks separately from `mix test` with: ```sh -npm test --workspace synixir-collaboration-example -- flowchart.spec.js +npm test --workspace synixir-collaboration-example -- flowchart.spec.ts ``` ## Try the collaborative table @@ -410,7 +415,7 @@ another tab to work on the same table. - Viewers can select and copy cells, but cannot change data, paste, or use undo. On mobile, the table scrolls horizontally while the row numbers stay visible. -The [table model](examples/collaboration/table/model.js) uses separate Y.Maps for +The [table model](examples/collaboration/table/model.ts) uses separate Y.Maps for row order, column order, column names, and deletion markers. Default row and column IDs are stable and require no initialization writes. Each cell uses a top-level Y.Text named `collaborative-table:cell:[row-id,column-id]:v1`, with the IDs encoded @@ -435,7 +440,7 @@ cells and table structure recover after a server restart. browser checks separately from `mix test` with: ```sh -npm test --workspace synixir-collaboration-example -- table.spec.js +npm test --workspace synixir-collaboration-example -- table.spec.ts ``` ## Editor presence and connection states @@ -484,7 +489,7 @@ quotas are described in the [operations guide](docs/operations.md). All JSON API routes fetch the session and use CSRF protection. First fetch `GET /api/session`, retain its `csrf_token` in memory, and send it as `x-csrf-token` for state-changing requests. Retain the returned cookies. Responses -use `Cache-Control: no-store`. Vite proxies `/api` and `/socket` to Phoenix. +use `Cache-Control: no-store`. The Next.js development server proxies `/api` and `/socket` to Phoenix. The Docker release serves the frontend, API, and WebSockets through one HTTPS origin. Production WebSocket origin checks include the scheme and port. Socket connections alone carry no identity: each room join must present an authorized @@ -684,7 +689,7 @@ an explicitly configured higher cap with sufficient server and browser memory. A client opts in with `chunked_sync: 1` in its authorized join parameters. The join reply includes `transfer` with `max_message_bytes`, `chunk_bytes`, `max_transfer_bytes`, and `transfer_timeout_ms`. The SDK's internal -[chunked-transport.js](packages/client/src/chunked-transport.js) wraps the +[chunked-transport.ts](packages/client/src/chunked-transport.ts) wraps the existing Phoenix channel provider. Clients without this option keep the original protocol and per-message size limit. @@ -852,7 +857,7 @@ It does not publish the package. Run browser tests separately from `mix test`; both use `synixir_test`. Playwright migrates the test database and starts its own Phoenix server on port -4010 and Vite on port 5174, then shuts them down. Those ports must be free; +4010 and Next.js on port 5174, then shuts them down. Those ports must be free; your development servers can keep running on ports 4000 and 5173. Each test uses unique room IDs so saved data from previous runs does not affect the assertions. The fixture sets `SYNIXIR_BROWSER_TEST=true` for its subprocesses, selecting a diff --git a/docs/research/deployment-release.md b/docs/research/deployment-release.md index 30ab764..72e2c54 100644 --- a/docs/research/deployment-release.md +++ b/docs/research/deployment-release.md @@ -39,7 +39,7 @@ Build the existing npm workspace with its lockfile and copy the Vite `dist` outp At the baseline, copying files alone is insufficient. `SynixirWeb.static_paths/0` excludes `index.html` and `sdk.html`, and the router has no root page route. `Plug.Static` does not automatically serve a directory index. Add deliberate routes for `/` and the SDK page, and serve their HTML with revalidation or `no-store`. Hashed Vite assets can use long immutable caching. Keep API and socket paths outside any HTML fallback. Sources: [web configuration](../../lib/synixir_web.ex), [router](../../lib/synixir_web/router.ex), [Plug.Static implementation](../../deps/plug/lib/plug/static.ex). -One origin preserves the SDK's default session/CSRF flow and avoids adding credentialed CORS. The default access callback uses `credentials: "same-origin"`; simply hosting the frontend on another origin changes its authentication behavior. Source: [SDK access implementation](../../packages/client/src/access.js). Serving files from nginx is possible, but packaging HTML and the API together makes image rollback and asset matching simpler for this milestone. +One origin preserves the SDK's default session/CSRF flow and avoids adding credentialed CORS. The default access callback uses `credentials: "same-origin"`; simply hosting the frontend on another origin changes its authentication behavior. Source: [SDK access implementation](../../packages/client/src/access.ts). Serving files from nginx is possible, but packaging HTML and the API together makes image rollback and asset matching simpler for this milestone. ## Make nginx the only trusted ingress diff --git a/docs/research/production-readiness.md b/docs/research/production-readiness.md index efb9db2..5ee5404 100644 --- a/docs/research/production-readiness.md +++ b/docs/research/production-readiness.md @@ -76,6 +76,6 @@ Archives contain private documents and authentication data. Use restrictive outp A browser load scenario can use the existing SDK and Playwright without editor rendering. Exercise multiple rooms, shared-room fanout, reconnect with offline edits, awareness, a chunked update, and a fresh observer after persistence. Count successful operations only after `saveStatus` reaches `saved` and observers converge. `connect()` resolves synchronization, not durability. Source: [SDK contract](../../packages/client/README.md). -Node 24 also has a built-in browser-compatible WebSocket, so a protocol load runner can import the SDK with an explicit `serverUrl` and custom `getAccess`. Its fetch calls do not acquire the browser example's login session automatically. This needs deliberate fixture authentication and is not evidence of browser UI performance. Sources: [Node WebSocket](https://nodejs.org/api/globals.html#class-websocket), [SDK implementation](../../packages/client/src/index.js). +Node 24 also has a built-in browser-compatible WebSocket, so a protocol load runner can import the SDK with an explicit `serverUrl` and custom `getAccess`. Its fetch calls do not acquire the browser example's login session automatically. This needs deliberate fixture authentication and is not evidence of browser UI performance. Sources: [Node WebSocket](https://nodejs.org/api/globals.html#class-websocket), [SDK implementation](../../packages/client/src/index.ts). Record offered rate, achieved rate, concurrent clients/rooms, update sizes, rejected requests, save and convergence percentiles, reconnect duration, server memory/run queue, database queue time, and generator CPU. If every client waits for an acknowledgement before its next edit, label the run closed-loop. It reduces offered load when the server slows and cannot establish an overload ceiling. Shared localhost resources, short runs, and small fixtures limit every capacity claim. Preserve raw results and distinguish a functional smoke result from measured production sizing. diff --git a/examples/collaboration/README.md b/examples/collaboration/README.md new file mode 100644 index 0000000..40ede8b --- /dev/null +++ b/examples/collaboration/README.md @@ -0,0 +1,44 @@ +# Next.js collaboration examples + +This app uses TypeScript, React, Next.js App Router, shadcn/ui (Radix), and +Tailwind CSS. Elixir/Phoenix owns accounts, room permissions, WebSockets, and +PostgreSQL persistence. The browser consumes `@synixir/client`. + +From the repository root, start Phoenix as described in the main README, then: + +```sh +npm ci +npm run dev --workspace synixir-collaboration-example +``` + +The `predev` step compiles the SDK. Open http://127.0.0.1:5173. The development +server in `dev.ts` proxies `/api` and `/socket` to Phoenix at +http://127.0.0.1:4000; set `SYNIXIR_ENDPOINT` to override it. The proxy preserves +the browser's Origin header so Phoenix can enforce its origin allowlist. + +`app/` contains the eight routes. `components/example-app.tsx` manages accounts, +room selection, and navigation. `components/workspace/workspace.tsx` owns each +SDK connection and its subscriptions, permissions, presence, and teardown. +The editor/canvas modules mount inside a React-owned host and release their +listeners and bindings when that host unmounts. They load only when selected. +`components/ui/` contains the generated shadcn primitives, and +`app/globals.css` contains Tailwind, theme tokens, and shared layout styles. + +```sh +npm run typecheck +npm test +npm run test:browser +npm run build +``` + +The build compiles the SDK to `packages/client/dist` and exports Next.js pages +and assets to `examples/collaboration/out`. Docker copies this export into +Phoenix's `priv/static`; the runtime needs no Node process. Existing public +URLs such as `/kanban.html?room=planning` and `/sdk.html?room=planning` are +preserved in development and production. Navigation uses full page loads to +retain the unsaved-edit warning and tear down old room connections. + +Browser tests run on ports 5174 and 4010 with a separate Phoenix test database. +They enable a development-only inspection adapter for public SDK and editor +APIs; production builds exclude it. The package consumer check also uses Vite +to verify that the published SDK works outside Next.js. diff --git a/examples/collaboration/app/flowchart/page.tsx b/examples/collaboration/app/flowchart/page.tsx new file mode 100644 index 0000000..d411819 --- /dev/null +++ b/examples/collaboration/app/flowchart/page.tsx @@ -0,0 +1,4 @@ +import { ExampleApp } from "@/components/example-app"; +export default function Page() { + return ; +} diff --git a/examples/collaboration/app/globals.css b/examples/collaboration/app/globals.css new file mode 100644 index 0000000..6158571 --- /dev/null +++ b/examples/collaboration/app/globals.css @@ -0,0 +1,244 @@ +@import "tailwindcss"; +@import "tw-animate-css"; +@import "shadcn/tailwind.css"; +@import "../kanban/board.css"; +@import "../whiteboard/canvas.css"; +@import "../flowchart/canvas.css"; +@import "../rich-text/editor.css"; +@import "../multiplayer-form/form.css"; +@import "../table/table.css"; + +@custom-variant dark (&:is(.dark *)); + +@theme inline { + --font-heading: var(--font-sans); + --font-sans: "Inter", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + --color-sidebar-ring: var(--sidebar-ring); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar: var(--sidebar); + --color-chart-5: var(--chart-5); + --color-chart-4: var(--chart-4); + --color-chart-3: var(--chart-3); + --color-chart-2: var(--chart-2); + --color-chart-1: var(--chart-1); + --color-ring: var(--ring); + --color-input: var(--input); + --color-border: var(--border); + --color-destructive: var(--destructive); + --color-accent-foreground: var(--accent-foreground); + --color-accent: var(--accent); + --color-muted-foreground: var(--muted-foreground); + --color-muted: var(--muted); + --color-secondary-foreground: var(--secondary-foreground); + --color-secondary: var(--secondary); + --color-primary-foreground: var(--primary-foreground); + --color-primary: var(--primary); + --color-popover-foreground: var(--popover-foreground); + --color-popover: var(--popover); + --color-card-foreground: var(--card-foreground); + --color-card: var(--card); + --color-foreground: var(--foreground); + --color-background: var(--background); + --radius-sm: calc(var(--radius) * 0.6); + --radius-md: calc(var(--radius) * 0.8); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) * 1.4); + --radius-2xl: calc(var(--radius) * 1.8); + --radius-3xl: calc(var(--radius) * 2.2); + --radius-4xl: calc(var(--radius) * 2.6); +} + +:root { + --background: oklch(0.984 0.002 260); + --foreground: oklch(0.145 0 0); + --card: oklch(1 0 0); + --card-foreground: oklch(0.145 0 0); + --popover: oklch(1 0 0); + --popover-foreground: oklch(0.145 0 0); + --primary: oklch(0.52 0.19 266); + --primary-foreground: oklch(0.985 0 0); + --secondary: oklch(0.97 0 0); + --secondary-foreground: oklch(0.205 0 0); + --muted: oklch(0.97 0 0); + --muted-foreground: oklch(0.556 0 0); + --accent: oklch(0.97 0 0); + --accent-foreground: oklch(0.205 0 0); + --destructive: oklch(0.577 0.245 27.325); + --border: oklch(0.922 0 0); + --input: oklch(0.922 0 0); + --ring: oklch(0.708 0 0); + --chart-1: oklch(0.87 0 0); + --chart-2: oklch(0.556 0 0); + --chart-3: oklch(0.439 0 0); + --chart-4: oklch(0.371 0 0); + --chart-5: oklch(0.269 0 0); + --radius: 0.625rem; + --sidebar: oklch(0.975 0.003 260); + --sidebar-foreground: oklch(0.145 0 0); + --sidebar-primary: oklch(0.205 0 0); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.97 0 0); + --sidebar-accent-foreground: oklch(0.205 0 0); + --sidebar-border: oklch(0.922 0 0); + --sidebar-ring: oklch(0.708 0 0); +} + +.dark { + --background: oklch(0.145 0 0); + --foreground: oklch(0.985 0 0); + --card: oklch(0.205 0 0); + --card-foreground: oklch(0.985 0 0); + --popover: oklch(0.205 0 0); + --popover-foreground: oklch(0.985 0 0); + --primary: oklch(0.922 0 0); + --primary-foreground: oklch(0.205 0 0); + --secondary: oklch(0.269 0 0); + --secondary-foreground: oklch(0.985 0 0); + --muted: oklch(0.269 0 0); + --muted-foreground: oklch(0.708 0 0); + --accent: oklch(0.269 0 0); + --accent-foreground: oklch(0.985 0 0); + --destructive: oklch(0.704 0.191 22.216); + --border: oklch(1 0 0 / 10%); + --input: oklch(1 0 0 / 15%); + --ring: oklch(0.556 0 0); + --chart-1: oklch(0.87 0 0); + --chart-2: oklch(0.556 0 0); + --chart-3: oklch(0.439 0 0); + --chart-4: oklch(0.371 0 0); + --chart-5: oklch(0.269 0 0); + --sidebar: oklch(0.205 0 0); + --sidebar-foreground: oklch(0.985 0 0); + --sidebar-primary: oklch(0.488 0.243 264.376); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.269 0 0); + --sidebar-accent-foreground: oklch(0.985 0 0); + --sidebar-border: oklch(1 0 0 / 10%); + --sidebar-ring: oklch(0.556 0 0); +} + +@layer base { + * { + @apply border-border outline-ring/50; + } + body { + @apply bg-background text-foreground; + } + html { + @apply font-sans; + } +} +@layer base { + button:not(:disabled), [role="button"]:not(:disabled) { cursor: pointer; } + [hidden] { display: none !important; } +} +.app-frame { display: grid; grid-template-columns: 224px minmax(0,1fr); min-height: 100vh; } +.app-sidebar { position: sticky; top: 0; height: 100vh; display: flex; flex-direction: column; padding: 30px 18px 20px; background: var(--sidebar); border-right: 1px solid var(--border); } +.brand { display: flex; gap: 10px; align-items: center; font-size: 19px; font-weight: 650; letter-spacing: -.6px; margin: 0 8px 36px; } +.brand-mark { display: grid; place-items: center; height: 34px; width: 34px; border-radius: 10px; background: var(--primary); color: var(--primary-foreground); } +.brand-caption { display: block; font-size: 9px; font-weight: 400; letter-spacing: 0; color: var(--muted-foreground); margin-top: 3px; } +.sidebar-section { margin: 0 12px 10px; font-size: 10px; text-transform: uppercase; letter-spacing: 1.4px; font-weight: 600; color: var(--muted-foreground); } +.sidebar-home, .nav-item { display: flex; align-items: center; gap: 11px; padding: 10px 12px; font-size: 12px; border-radius: 7px; color: var(--muted-foreground); transition: background .15s; } +.sidebar-home:hover, .nav-item:hover { background: var(--accent); color: var(--foreground); } +.example-navigation { display: flex; flex-direction: column; gap: 4px; } +.nav-item-active { background: var(--card); color: var(--primary); box-shadow: 0 1px 3px #00000009; font-weight: 600; } +.nav-dot { height: 5px; width: 5px; border-radius: 50%; background: var(--primary); margin-left: auto; } +.sidebar-bottom { margin-top: auto; display: flex; flex-direction: column; gap: 18px; padding: 20px 6px 0; } +.app-main { display: flex; flex-direction: column; min-width: 0; } +.app-topbar { min-height: 76px; padding: 0 36px; border-bottom: 1px solid var(--border); display: flex; align-items: center; justify-content: space-between; background: var(--card); gap: 12px; } +.main-content { width: 100%; max-width: 1600px; margin: 0 auto; padding: 36px; flex: 1; } +.page-heading { display: flex; align-items: center; justify-content: space-between; gap: 16px; margin-bottom: 30px; } +.eyebrow { color: var(--primary); letter-spacing: 2px; font-size: 9px; font-weight: 650; margin-bottom: 10px; } +.page-heading h1 { font-size: clamp(26px, 2.6vw, 34px); line-height: 1.2; font-weight: 620; letter-spacing: -1.1px; } +.page-description { margin-top: 9px; font-size: 13px; color: var(--muted-foreground); } +.presence-dot { width: 5px; height: 5px; border-radius: 50%; background: var(--primary); } +.welcome-grid { display: grid; grid-template-columns: minmax(320px, 440px) minmax(240px,1fr); gap: 70px; align-items: center; padding: 24px 0 48px; } +.welcome-note { max-width: 340px; justify-self: center; text-align: center; } +.welcome-note h2 { font-size: 20px; font-weight: 550; letter-spacing: -.6px; margin: 24px 0 10px; } +.welcome-note p { font-size: 13px; line-height: 1.7; color: var(--muted-foreground); } +.welcome-art { position: relative; height: 170px; width: 200px; margin: 0 auto; } +.art-card { position: absolute; top: 8px; left: 30px; height: 156px; width: 150px; padding: 22px; border: 1px solid var(--border); border-radius: 13px; background: var(--card); transform: rotate(7deg); color: var(--primary); box-shadow: 0 12px 28px #00000006; } +.art-card-back { transform: rotate(-12deg); left: 4px; top: 0; background: var(--muted); } +.art-line { margin-top: 16px; height: 5px; border-radius: 10px; background: var(--border); } +.art-line.short { width: 64%; margin-top: 8px; } +.art-avatars { display: flex; margin-top: 16px; } +.art-avatars span { border: 2px solid var(--card); border-radius: 50%; background: var(--secondary); width: 25px; height: 25px; font-size: 8px; display: grid; place-items: center; margin-right: -6px; } +.rooms-grid { display: grid; grid-template-columns: minmax(0,1.3fr) minmax(270px,1fr); gap: 20px; } +.room-list { display: flex; flex-direction: column; gap: 5px; } +.room-list a { display: flex; gap: 12px; align-items: center; border: 1px solid var(--border); border-radius: 8px; padding: 13px; font-size: 13px; } +.room-list a:hover { background: var(--accent); } +.room-icon { color: var(--primary); background: var(--secondary); border-radius: 7px; padding: 8px; } +.section-heading { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; } +.section-heading h2 { font-size: 15px; font-weight: 550; } +.section-heading>span { font-size: 11px; color: var(--muted-foreground); } +.examples-grid { display: grid; grid-template-columns: repeat(3,minmax(0,1fr)); gap: 12px; } +.example-card { position: relative; border: 1px solid var(--border); background: var(--card); border-radius: 9px; padding: 22px; } +.example-card>svg:first-child { color: var(--primary); margin-bottom: 20px; } +.example-card h3 { font-size: 13px; font-weight: 550; margin-bottom: 6px; } +.example-card p { font-size: 11px; line-height: 1.6; color: var(--muted-foreground); max-width: 180px; } +.example-card:hover { border-color: var(--ring); } +.example-arrow { position: absolute; top: 22px; right: 20px; color: var(--muted-foreground); } +.workspace-status { display: flex; gap: 16px; justify-content: space-between; align-items: center; } +#status { font-size: 12px; color: var(--muted-foreground); } +#status[data-state="connected"] { color: var(--primary); } +#status[data-state="error"] { color: var(--destructive); } +.workspace-grid { display: grid; grid-template-columns: minmax(0,1fr) 260px; gap: 22px; align-items: start; } +.room-sidebar { display: flex; flex-direction: column; gap: 16px; } +.document-panel { border: 1px solid var(--border); border-radius: 12px; background: var(--card); overflow: hidden; } +.editor-toolbar { padding: 10px 16px; border-bottom: 1px solid var(--border); display: flex; align-items: center; justify-content: space-between; gap: 12px; } +.document-title { font-size: 12px; font-weight: 550; overflow-wrap: anywhere; } +.editor-footer { display: flex; align-items: center; justify-content: space-between; padding: 12px 18px; border-top: 1px solid var(--border); font-size: 10px; color: var(--muted-foreground); } +.editor-footer-dot { height: 5px; width: 5px; border-radius: 50%; background: var(--primary); } +.app-footer { padding: 16px 36px; display: flex; justify-content: space-between; border-top: 1px solid var(--border); font-size: 10px; color: var(--muted-foreground); } +#collaboration { background: transparent; color: var(--foreground); } +#collaboration .document-panel { background: var(--card); } +.surface > .cm-editor { font-size: 15px; min-height: 440px; } +.surface > .cm-editor .cm-content { padding: 28px 30px 70px; font-family: inherit; line-height: 1.8; caret-color: var(--primary); } +.surface .cm-focused { outline: none; } +.surface .cm-placeholder { color: var(--muted-foreground); } +.surface .cm-ySelectionCaret { position: relative; border-left: 1px solid; border-right: 1px solid; margin-left: -1px; margin-right: -1px; } +.surface .cm-ySelectionInfo { position: absolute; top: -1.1em; left: -1px; font-size: 10px; font-weight: 500; padding: 1px 4px; white-space: nowrap; color: white; border-radius: 3px 3px 3px 0; } +.surface .cm-ySelection { background-color: #3565b026; } +.surface button { border: 1px solid var(--input); border-radius: 6px; padding: 7px 11px; font-size: 12px; background: var(--card); color: var(--foreground); } +.surface button:hover:not(:disabled) { background: var(--accent); } +.surface button:disabled { opacity: .45; cursor: default; } +.surface input:not([type="checkbox"]), .surface textarea, .surface select { border: 1px solid var(--input); border-radius: 6px; padding: 8px 10px; font: inherit; font-size: 13px; background: var(--card); color: var(--foreground); } +.surface label { display: block; font-size: 12px; margin: 10px 0 6px; } +.surface .note { color: var(--muted-foreground); font-size: 12px; } +.surface dialog { margin: auto; padding: 24px; border: 1px solid var(--border); border-radius: 12px; background: var(--card); color: var(--foreground); } +.surface dialog::backdrop { background: #0006; } +.surface .actions { display: flex; gap: 8px; margin-top: 14px; } +.surface .kanban-board { min-height: 440px; } +.surface .card-open { width: 100%; text-align: left; border: 0; } +.surface .card-list .kanban-card { background: var(--card); } +.surface .kanban-card .card-open { background: transparent; } +.surface .card-dialog input,.surface .card-dialog textarea { width: 100%; } +.surface .whiteboard-tools, .surface .canvas-summary, .surface .object-inspector, .surface .format-toolbar, .surface .table-tools { background: var(--card); border-color: var(--border); color: var(--foreground); } +.surface .whiteboard-canvas { color: #223e33; } +.surface .whiteboard-object { color: #223e33; } +.surface .shared-table { color: #223e33; } +@media (max-width:1200px) { .app-frame { grid-template-columns: 190px minmax(0,1fr); } .main-content { padding: 28px 24px; } .workspace-grid { grid-template-columns: minmax(0,1fr); } .room-sidebar { display: grid; grid-template-columns: repeat(2,minmax(0,1fr)); } } +@media (max-width:800px) { .app-frame { grid-template-columns: minmax(0,1fr); } .app-sidebar { position: static; height: auto; padding: 14px 18px; border-right: 0; border-bottom: 1px solid var(--border); } .brand { margin: 0 0 14px; } .sidebar-section,.sidebar-home,.sidebar-bottom { display: none; } .example-navigation { flex-direction: row; overflow: auto; } .nav-item { flex: 0 0 auto; font-size: 11px; } .app-topbar { padding: 14px 20px; min-height: 60px; } .main-content { padding: 24px 18px; } .page-heading { align-items: start; } .page-heading>div+[data-slot="badge"] { display: none; } .welcome-grid,.rooms-grid { grid-template-columns: 1fr; gap: 28px; } .welcome-note { display: none; } .examples-grid { grid-template-columns: repeat(2,minmax(0,1fr)); } .room-sidebar { grid-template-columns: 1fr; } .workspace-status { flex-wrap: wrap; } .app-footer { padding: 15px 20px; } } +@media (prefers-reduced-motion: reduce) { *,*::before,*::after { animation-duration: .01ms !important; transition-duration: .01ms !important; } } +/* Keep long account names within the header at narrow widths. */ +.app-sidebar, #account-bar { min-width: 0; } +#account-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +#participants li > .text-sm { min-width: 0; overflow: hidden; text-overflow: ellipsis; } +.surface > .cm-editor .cm-scroller { font-family: var(--font-sans); } +@media (max-width:800px) { + .app-topbar { flex-wrap: wrap; } + #account-bar { max-width: 100%; } + #account-name { max-width: 160px; } + .editor-toolbar { flex-wrap: wrap; } +} +/* A placeholder occupies document position zero without shifting peer carets. */ +.surface .cm-line { position: relative; padding: 0; } +.surface .cm-placeholder { position: absolute; inset-inline: 0; top: 0; pointer-events: none; } +.rich-document ul { list-style-type: disc; } +.rich-document ol { list-style-type: decimal; } +.rich-document h1, .rich-document h2, .rich-document h3 { font-weight: 600; } diff --git a/examples/collaboration/app/kanban/page.tsx b/examples/collaboration/app/kanban/page.tsx new file mode 100644 index 0000000..dfccce3 --- /dev/null +++ b/examples/collaboration/app/kanban/page.tsx @@ -0,0 +1,4 @@ +import { ExampleApp } from "@/components/example-app"; +export default function Page() { + return ; +} diff --git a/examples/collaboration/app/layout.tsx b/examples/collaboration/app/layout.tsx new file mode 100644 index 0000000..79dbb50 --- /dev/null +++ b/examples/collaboration/app/layout.tsx @@ -0,0 +1,13 @@ +import type { Metadata } from "next"; +import "./globals.css"; +export const metadata: Metadata = { + title: "Synixir · Work together", + description: "A shared workspace for writing, planning, and creating together.", +}; +export default function RootLayout({ children }: { children: React.ReactNode }) { + return ( + + {children} + + ); +} diff --git a/examples/collaboration/app/multiplayer-form/page.tsx b/examples/collaboration/app/multiplayer-form/page.tsx new file mode 100644 index 0000000..d3e50cb --- /dev/null +++ b/examples/collaboration/app/multiplayer-form/page.tsx @@ -0,0 +1,4 @@ +import { ExampleApp } from "@/components/example-app"; +export default function Page() { + return ; +} diff --git a/examples/collaboration/app/page.tsx b/examples/collaboration/app/page.tsx new file mode 100644 index 0000000..ee6689b --- /dev/null +++ b/examples/collaboration/app/page.tsx @@ -0,0 +1,4 @@ +import { ExampleApp } from "@/components/example-app"; +export default function Page() { + return ; +} diff --git a/examples/collaboration/app/rich-text/page.tsx b/examples/collaboration/app/rich-text/page.tsx new file mode 100644 index 0000000..5bd3ca4 --- /dev/null +++ b/examples/collaboration/app/rich-text/page.tsx @@ -0,0 +1,4 @@ +import { ExampleApp } from "@/components/example-app"; +export default function Page() { + return ; +} diff --git a/examples/collaboration/app/sdk/page.tsx b/examples/collaboration/app/sdk/page.tsx new file mode 100644 index 0000000..293797a --- /dev/null +++ b/examples/collaboration/app/sdk/page.tsx @@ -0,0 +1,4 @@ +import { ExampleApp } from "@/components/example-app"; +export default function Page() { + return ; +} diff --git a/examples/collaboration/app/table/page.tsx b/examples/collaboration/app/table/page.tsx new file mode 100644 index 0000000..e3f7a23 --- /dev/null +++ b/examples/collaboration/app/table/page.tsx @@ -0,0 +1,4 @@ +import { ExampleApp } from "@/components/example-app"; +export default function Page() { + return ; +} diff --git a/examples/collaboration/app/whiteboard/page.tsx b/examples/collaboration/app/whiteboard/page.tsx new file mode 100644 index 0000000..b1392d2 --- /dev/null +++ b/examples/collaboration/app/whiteboard/page.tsx @@ -0,0 +1,4 @@ +import { ExampleApp } from "@/components/example-app"; +export default function Page() { + return ; +} diff --git a/examples/collaboration/components.json b/examples/collaboration/components.json new file mode 100644 index 0000000..02e61e0 --- /dev/null +++ b/examples/collaboration/components.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "radix-nova", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "", + "css": "app/globals.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "iconLibrary": "lucide", + "rtl": false, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "menuColor": "default", + "menuAccent": "subtle", + "registries": {} +} diff --git a/examples/collaboration/components/example-app.tsx b/examples/collaboration/components/example-app.tsx new file mode 100644 index 0000000..0c9d820 --- /dev/null +++ b/examples/collaboration/components/example-app.tsx @@ -0,0 +1,450 @@ +"use client"; +import { useEffect, useRef, useState, type FormEvent } from "react"; +import { + ArrowUpRight, + ChevronRight, + CircleHelp, + FolderPlus, + LogOut, + Moon, + Sun, + Users, + Zap, +} from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { Badge } from "@/components/ui/badge"; +import { + Card, + CardHeader, + CardTitle, + CardDescription, + CardContent, + CardFooter, +} from "@/components/ui/card"; +import { Field, FieldGroup, FieldLabel, FieldDescription } from "@/components/ui/field"; +import { Input } from "@/components/ui/input"; +import { Alert, AlertDescription } from "@/components/ui/alert"; +import { Avatar, AvatarFallback } from "@/components/ui/avatar"; +import { Skeleton } from "@/components/ui/skeleton"; +import { + Empty, + EmptyHeader, + EmptyTitle, + EmptyDescription, + EmptyMedia, +} from "@/components/ui/empty"; +import { Separator } from "@/components/ui/separator"; +import { example, examples, type ExampleKind } from "@/lib/examples"; +import { api, explain, sessionChanged, type User, type Session, type RoomSummary } from "@/lib/api"; +import { cn } from "@/lib/utils"; +import { Workspace } from "./workspace/workspace"; + +export function ExampleApp({ kind }: { kind: ExampleKind }) { + const selected = example(kind); + const [user, setUser] = useState(null); + const [loading, setLoading] = useState(true); + const [roomId, setRoomId] = useState(null); + const [rooms, setRooms] = useState([]); + const [error, setError] = useState(""); + const [pending, setPending] = useState(false); + const [dark, setDark] = useState(false); + const cleanup = useRef<() => void>(() => {}); + const unsaved = useRef<() => boolean>(() => false); + + useEffect(() => { + const controller = new AbortController(); + const id = new URL(location.href).searchParams.get("room"); + setRoomId(id); + try { + const value = localStorage.getItem("synixir:theme") === "dark"; + setDark(value); + document.documentElement.classList.toggle("dark", value); + } catch { + /* Optional preference. */ + } + void api("/api/session", "GET", undefined, controller.signal) + .then(async (session) => { + setUser(session.user); + if (session.user && !id) + setRooms( + (await api<{ data: RoomSummary[] }>("/api/rooms", "GET", undefined, controller.signal)) + .data, + ); + }) + .catch((error) => { + if (!controller.signal.aborted) setError(explain(error)); + }) + .finally(() => { + if (!controller.signal.aborted) setLoading(false); + }); + const changed = (event: StorageEvent) => { + if (event.key === "synixir:session-change") { + cleanup.current(); + location.reload(); + } + }; + const restored = (event: PageTransitionEvent) => { + if (event.persisted) location.reload(); + }; + window.addEventListener("storage", changed); + window.addEventListener("pageshow", restored); + return () => { + controller.abort(); + window.removeEventListener("storage", changed); + window.removeEventListener("pageshow", restored); + }; + }, []); + + async function authenticate(event: FormEvent) { + event.preventDefault(); + const values = Object.fromEntries(new FormData(event.currentTarget)); + const submitter = (event.nativeEvent as SubmitEvent).submitter as HTMLButtonElement; + setPending(true); + setError(""); + try { + await api(submitter.value === "register" ? "/api/accounts" : "/api/session", "POST", values); + sessionChanged(); + location.reload(); + } catch (error) { + setError(explain(error)); + setPending(false); + } + } + async function signOut() { + if ( + unsaved.current() && + !confirm("Sign out and discard unsaved changes? Copy your draft first if you need it.") + ) + return; + try { + await api("/api/session", "DELETE"); + cleanup.current(); + sessionChanged(); + location.reload(); + } catch (error) { + setError(explain(error)); + } + } + async function createRoom(event: FormEvent) { + event.preventDefault(); + setPending(true); + setError(""); + try { + const result = await api<{ data: { id: string } }>( + "/api/rooms", + "POST", + Object.fromEntries(new FormData(event.currentTarget)), + ); + location.href = `${selected.path}?room=${encodeURIComponent(result.data.id)}`; + } catch (error) { + setError(explain(error)); + setPending(false); + } + } + function theme() { + const value = !dark; + setDark(value); + document.documentElement.classList.toggle("dark", value); + try { + localStorage.setItem("synixir:theme", value ? "dark" : "light"); + } catch { + /* Optional preference. */ + } + } + + return ( +
+ +
+
+
+ Workspace + + {selected.title} +
+ {user ? ( +
+ + {user.username.slice(0, 2).toUpperCase()} + + + {user.username} + + +
+ ) : ( + Live collaboration + )} +
+
+
+
+

BETTER TOGETHER

+

{selected.title}

+

{selected.description}

+
+ + + Shared in real time + +
+ {error && ( + + {error} + + )} + {!error && } + {loading ? ( +
+ + +
+ ) : !user ? ( +
+ + + + Welcome to your shared workspace + + + Sign in or create an account to start collaborating. + + + +
+ + + Username + + + + Password + + + Use at least 15 characters. Usernames use letters, numbers, underscores or + hyphens. + + +
+ + +
+
+
+
+ +

+ Your account works across every example. +

+
+
+
+
+
+ Synixir + Shared work, saved as you go. +
+
+
+ ); +} diff --git a/examples/collaboration/components/ui/alert.tsx b/examples/collaboration/components/ui/alert.tsx new file mode 100644 index 0000000..17ba728 --- /dev/null +++ b/examples/collaboration/components/ui/alert.tsx @@ -0,0 +1,75 @@ +import * as React from "react" +import { cva, type VariantProps } from "class-variance-authority" +import { cn } from "cn" + +const alertVariants = cva( + "group/alert relative grid w-full gap-0.5 rounded-lg border px-2.5 py-2 text-left text-sm has-data-[slot=alert-action]:relative has-data-[slot=alert-action]:pr-18 has-[>svg]:grid-cols-[auto_1fr] has-[>svg]:gap-x-2 *:[svg]:row-span-2 *:[svg]:translate-y-0.5 *:[svg]:text-current *:[svg:not([class*='size-'])]:size-4", + { + variants: { + variant: { + default: "bg-card text-card-foreground", + destructive: + "bg-card text-destructive *:data-[slot=alert-description]:text-destructive/90 *:[svg]:text-current", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +function Alert({ + className, + variant, + ...props +}: React.ComponentProps<"div"> & VariantProps) { + return ( +
+ ) +} + +function AlertTitle({ className, ...props }: React.ComponentProps<"div">) { + return ( +
svg]/alert:col-start-2 [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground", + className + )} + {...props} + /> + ) +} + +function AlertDescription({ + className, + ...props +}: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function AlertAction({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +export { Alert, AlertTitle, AlertDescription, AlertAction } diff --git a/examples/collaboration/components/ui/avatar.tsx b/examples/collaboration/components/ui/avatar.tsx new file mode 100644 index 0000000..4b0a08b --- /dev/null +++ b/examples/collaboration/components/ui/avatar.tsx @@ -0,0 +1,111 @@ +"use client" + +import * as React from "react" +import { cn } from "cn" +import { Avatar as AvatarPrimitive } from "radix-ui" + +function Avatar({ + className, + size = "default", + ...props +}: React.ComponentProps & { + size?: "default" | "sm" | "lg" +}) { + return ( + + ) +} + +function AvatarImage({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AvatarFallback({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AvatarBadge({ className, ...props }: React.ComponentProps<"span">) { + return ( + svg]:hidden", + "group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2", + "group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2", + className + )} + {...props} + /> + ) +} + +function AvatarGroup({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function AvatarGroupCount({ + className, + ...props +}: React.ComponentProps<"div">) { + return ( +
svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3", + className + )} + {...props} + /> + ) +} + +export { + Avatar, + AvatarImage, + AvatarFallback, + AvatarGroup, + AvatarGroupCount, + AvatarBadge, +} diff --git a/examples/collaboration/components/ui/badge.tsx b/examples/collaboration/components/ui/badge.tsx new file mode 100644 index 0000000..837196c --- /dev/null +++ b/examples/collaboration/components/ui/badge.tsx @@ -0,0 +1,48 @@ +import * as React from "react" +import { cva, type VariantProps } from "class-variance-authority" +import { cn } from "cn" +import { Slot } from "radix-ui" + +const badgeVariants = cva( + "group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!", + { + variants: { + variant: { + default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80", + secondary: + "bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80", + destructive: + "bg-destructive/10 text-destructive focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 [a]:hover:bg-destructive/20", + outline: + "border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground", + ghost: + "hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50", + link: "text-primary underline-offset-4 hover:underline", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +function Badge({ + className, + variant = "default", + asChild = false, + ...props +}: React.ComponentProps<"span"> & + VariantProps & { asChild?: boolean }) { + const Comp = asChild ? Slot.Root : "span" + + return ( + + ) +} + +export { Badge, badgeVariants } diff --git a/examples/collaboration/components/ui/button.tsx b/examples/collaboration/components/ui/button.tsx new file mode 100644 index 0000000..18407c9 --- /dev/null +++ b/examples/collaboration/components/ui/button.tsx @@ -0,0 +1,66 @@ +import * as React from "react" +import { cva, type VariantProps } from "class-variance-authority" +import { cn } from "cn" +import { Slot } from "radix-ui" + +const buttonVariants = cva( + "group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", + { + variants: { + variant: { + default: "bg-primary text-primary-foreground hover:bg-primary/80", + outline: + "border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50", + secondary: + "bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground", + ghost: + "hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50", + destructive: + "bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40", + link: "text-primary underline-offset-4 hover:underline", + }, + size: { + default: + "h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2", + xs: "h-6 gap-1 rounded-[min(var(--radius-md),10px)] px-2 text-xs in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3", + sm: "h-7 gap-1 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5", + lg: "h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2", + icon: "size-8", + "icon-xs": + "size-6 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-lg [&_svg:not([class*='size-'])]:size-3", + "icon-sm": + "size-7 rounded-[min(var(--radius-md),12px)] in-data-[slot=button-group]:rounded-lg", + "icon-lg": "size-9", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + } +) + +function Button({ + className, + variant = "default", + size = "default", + asChild = false, + ...props +}: React.ComponentProps<"button"> & + VariantProps & { + asChild?: boolean + }) { + const Comp = asChild ? Slot.Root : "button" + + return ( + + ) +} + +export { Button, buttonVariants } diff --git a/examples/collaboration/components/ui/card.tsx b/examples/collaboration/components/ui/card.tsx new file mode 100644 index 0000000..cf02f48 --- /dev/null +++ b/examples/collaboration/components/ui/card.tsx @@ -0,0 +1,102 @@ +import * as React from "react" +import { cn } from "cn" + +function Card({ + className, + size = "default", + ...props +}: React.ComponentProps<"div"> & { size?: "default" | "sm" }) { + return ( +
img:first-child]:pt-0 data-[size=sm]:[--card-spacing:--spacing(3)] data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl", + className + )} + {...props} + /> + ) +} + +function CardHeader({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardTitle({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardDescription({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardAction({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardContent({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardFooter({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +export { + Card, + CardHeader, + CardFooter, + CardTitle, + CardAction, + CardDescription, + CardContent, +} diff --git a/examples/collaboration/components/ui/empty.tsx b/examples/collaboration/components/ui/empty.tsx new file mode 100644 index 0000000..e11ce34 --- /dev/null +++ b/examples/collaboration/components/ui/empty.tsx @@ -0,0 +1,103 @@ +import { cva, type VariantProps } from "class-variance-authority" +import { cn } from "cn" + +function Empty({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function EmptyHeader({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +const emptyMediaVariants = cva( + "mb-2 flex shrink-0 items-center justify-center [&_svg]:pointer-events-none [&_svg]:shrink-0", + { + variants: { + variant: { + default: "bg-transparent", + icon: "flex size-8 shrink-0 items-center justify-center rounded-lg bg-muted text-foreground [&_svg:not([class*='size-'])]:size-4", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +function EmptyMedia({ + className, + variant = "default", + ...props +}: React.ComponentProps<"div"> & VariantProps) { + return ( +
+ ) +} + +function EmptyTitle({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function EmptyDescription({ className, ...props }: React.ComponentProps<"p">) { + return ( +
a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary", + className + )} + {...props} + /> + ) +} + +function EmptyContent({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +export { + Empty, + EmptyHeader, + EmptyTitle, + EmptyDescription, + EmptyContent, + EmptyMedia, +} diff --git a/examples/collaboration/components/ui/field.tsx b/examples/collaboration/components/ui/field.tsx new file mode 100644 index 0000000..8ae7f29 --- /dev/null +++ b/examples/collaboration/components/ui/field.tsx @@ -0,0 +1,238 @@ +"use client" + +import { useMemo } from "react" +import { cva, type VariantProps } from "class-variance-authority" +import { cn } from "cn" + +import { Label } from "@/components/ui/label" +import { Separator } from "@/components/ui/separator" + +function FieldSet({ className, ...props }: React.ComponentProps<"fieldset">) { + return ( +
[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3", + className + )} + {...props} + /> + ) +} + +function FieldLegend({ + className, + variant = "legend", + ...props +}: React.ComponentProps<"legend"> & { variant?: "legend" | "label" }) { + return ( + + ) +} + +function FieldGroup({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +const fieldVariants = cva( + "group/field flex w-full gap-2 data-[invalid=true]:text-destructive", + { + variants: { + orientation: { + vertical: "flex-col *:w-full [&>.sr-only]:w-auto", + horizontal: + "flex-row items-center has-[>[data-slot=field-content]]:items-start *:data-[slot=field-label]:flex-auto has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px", + responsive: + "flex-col *:w-full @md/field-group:flex-row @md/field-group:items-center @md/field-group:*:w-auto @md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:*:data-[slot=field-label]:flex-auto [&>.sr-only]:w-auto @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px", + }, + }, + defaultVariants: { + orientation: "vertical", + }, + } +) + +function Field({ + className, + orientation = "vertical", + ...props +}: React.ComponentProps<"div"> & VariantProps) { + return ( +
+ ) +} + +function FieldContent({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function FieldLabel({ + className, + ...props +}: React.ComponentProps) { + return ( +
+ ); +} diff --git a/examples/collaboration/dev.ts b/examples/collaboration/dev.ts new file mode 100644 index 0000000..8171538 --- /dev/null +++ b/examples/collaboration/dev.ts @@ -0,0 +1,37 @@ +import { createServer } from "node:http"; +import next from "next"; +import httpProxy from "http-proxy"; +const index = process.argv.indexOf("--port"); +const port = index >= 0 ? Number(process.argv[index + 1]) : 5173; +const app = next({ dev: true, hostname: "127.0.0.1", port }); +const proxy = httpProxy.createProxyServer({ + target: process.env.SYNIXIR_ENDPOINT ?? "http://127.0.0.1:4000", + ws: true, +}); +proxy.on("error", (_error, _request, response) => { + if (response && "writeHead" in response) { + response.writeHead(502); + response.end("Backend unavailable"); + } +}); +await app.prepare(); +const handle = app.getRequestHandler(); +const server = createServer((request, response) => { + if (/^\/(api|socket)(\/|\?|$)/.test(request.url ?? "")) return proxy.web(request, response); + // Keep the published example URLs working in development and static exports. + request.url = request.url?.replace(/^([^?]*)\.html(?=\?|$)/, "$1"); + void handle(request, response); +}); +server.on("upgrade", (request, socket, head) => { + if (request.url?.startsWith("/socket")) proxy.ws(request, socket, head); + else void app.getUpgradeHandler()(request, socket, head); +}); +server.listen(port, "127.0.0.1", () => console.log(`Synixir examples: http://127.0.0.1:${port}`)); +async function stop() { + proxy.close(); + server.close(); + await app.close(); + process.exit(0); +} +process.on("SIGTERM", () => void stop()); +process.on("SIGINT", () => void stop()); diff --git a/examples/collaboration/editor.js b/examples/collaboration/editor.ts similarity index 80% rename from examples/collaboration/editor.js rename to examples/collaboration/editor.ts index 44c01aa..75a1889 100644 --- a/examples/collaboration/editor.js +++ b/examples/collaboration/editor.ts @@ -1,22 +1,23 @@ +import { inspectForTest } from "./lib/browser-test.ts"; import { EditorView, drawSelection, keymap, placeholder } from "@codemirror/view"; import { Compartment, EditorState } from "@codemirror/state"; import { defaultKeymap } from "@codemirror/commands"; import { yCollab, yUndoManagerKeymap } from "y-codemirror.next"; import * as Y from "yjs"; -export function createEditor(text, awareness) { +export function createEditor(text: Y.Text, awareness: import("y-protocols/awareness").Awareness) { let readOnly = true; const permission = new Compartment(); // Yjs history commands bypass CodeMirror's readOnly state. const historyKeymap = yUndoManagerKeymap.map(binding => ({ - ...binding, run: view => readOnly || binding.run(view), + ...binding, run: (view: EditorView) => readOnly || (binding.run?.(view) ?? false), })); const undoManager = new Y.UndoManager(text, { trackedOrigins: new Set() }); - const undo = document.querySelector("#undo"); - const redo = document.querySelector("#redo"); + const undo = document.querySelector("#undo")!; + const redo = document.querySelector("#redo")!; const editor = new EditorView({ doc: text.toString(), - parent: document.querySelector("#editor"), + parent: document.querySelector("#editor")!, extensions: [ permission.of(EditorState.readOnly.of(true)), // Use Yjs history so undo never removes another participant's edits. @@ -36,6 +37,8 @@ export function createEditor(text, awareness) { ], }); + inspectForTest({ editor }); + function showHistory() { undo.disabled = readOnly || !undoManager.canUndo(); redo.disabled = readOnly || !undoManager.canRedo(); @@ -55,7 +58,7 @@ export function createEditor(text, awareness) { editor.destroy(); undoManager.destroy(); }; - destroy.setReadOnly = value => { + destroy.setReadOnly = (value: boolean) => { if (readOnly === value) return; readOnly = value; editor.dispatch({ effects: permission.reconfigure(EditorState.readOnly.of(value)) }); diff --git a/examples/collaboration/example-shell.js b/examples/collaboration/example-shell.js deleted file mode 100644 index 4caa8ab..0000000 --- a/examples/collaboration/example-shell.js +++ /dev/null @@ -1,275 +0,0 @@ -import { SynixirRoom } from "@synixir/client"; -import { showParticipants } from "./presence.js"; -import "./style.css"; - -export function startExample({ createSurface, pagePath = "/", presenceOptions }) { - const roomId = new URL(window.location.href).searchParams.get("room"); - let csrf; - let clearCollaboration = () => {}; - let hasUnsavedChanges = () => false; - - async function api(path, { method = "GET", body } = {}) { - const response = await fetch(path, { - method, credentials: "same-origin", signal: AbortSignal.timeout(10_000), - headers: { "Content-Type": "application/json", ...(csrf ? { "x-csrf-token": csrf } : {}) }, - body: body ? JSON.stringify(body) : undefined, - }); - const result = await response.json(); - if (!response.ok) throw new Error(result.error ?? "request_failed"); - if (result.csrf_token) csrf = result.csrf_token; - return result; - } - - function explain(error) { - return { - invalid_credentials: "Username or password is incorrect.", - invalid_registration: "Choose an available username and a password of 15–128 characters.", - rate_limited: "Too many attempts. Wait a minute and try again.", - unauthorized: "Access expired or denied", - forbidden: "Only an owner can manage room access.", - last_owner: "Add another owner before removing or changing the last owner.", - room_unavailable: "That room ID is unavailable. Choose another.", - room_quota: "This account has reached its room ownership limit. Ask an operator to review the limit.", - account_not_found: "No account has that username.", - }[error.message] ?? "The request failed. Please try again."; - } - - function sessionChanged() { - try { localStorage.setItem("synixir:session-change", crypto.randomUUID()); } catch { /* Storage may be disabled. */ } - } - window.addEventListener("storage", event => { - if (event.key === "synixir:session-change") { - clearCollaboration(); - window.location.reload(); - } - }); - - document.querySelector("#auth-form").addEventListener("submit", async event => { - event.preventDefault(); - const form = event.currentTarget; - const values = Object.fromEntries(new FormData(form)); - const path = event.submitter.value === "register" ? "/api/accounts" : "/api/session"; - const buttons = [...form.querySelectorAll("button")]; - buttons.forEach(button => button.disabled = true); - try { - await api(path, { method: "POST", body: values }); - sessionChanged(); - window.location.reload(); - } catch (error) { - document.querySelector("#account-error").textContent = explain(error); - buttons.forEach(button => button.disabled = false); - } - }); - - document.querySelector("#sign-out").addEventListener("click", async () => { - if (hasUnsavedChanges() && !window.confirm("Sign out and discard unsaved changes? Copy your draft first if you need it.")) return; - try { - await api("/api/session", { method: "DELETE" }); - clearCollaboration(); - sessionChanged(); - window.location.reload(); - } catch (error) { document.querySelector("#account-error").textContent = explain(error); } - }); - - document.querySelector("#create-room-form").addEventListener("submit", async event => { - event.preventDefault(); - try { - const { data } = await api("/api/rooms", { method: "POST", body: Object.fromEntries(new FormData(event.currentTarget)) }); - window.location.href = `${pagePath}?room=${encodeURIComponent(data.id)}`; - } catch (error) { document.querySelector("#account-error").textContent = explain(error); } - }); - - async function boot() { - try { - const { user } = await api("/api/session"); - document.querySelector("#auth-panel").hidden = Boolean(user); - document.querySelector("#account-bar").hidden = !user; - if (!user) return; - document.querySelector("#account-name").textContent = user.username; - if (roomId) { - document.querySelector("#collaboration").hidden = false; - startCollaboration(user); - } else { - document.querySelector("#rooms-panel").hidden = false; - const { data } = await api("/api/rooms"); - document.querySelector("#room-list").replaceChildren(...data.map(room => { - const item = document.createElement("li"); - const link = document.createElement("a"); - link.href = `${pagePath}?room=${encodeURIComponent(room.id)}`; - link.textContent = `${room.id} · ${room.role}`; - item.append(link); - return item; - })); - } - } catch (error) { document.querySelector("#account-error").textContent = explain(error); } - } - document.querySelectorAll("[data-room-home]").forEach(link => { link.href = pagePath; }); - if (roomId) document.querySelectorAll("#other-example, [data-example-link]").forEach(link => { - link.href += `?room=${encodeURIComponent(roomId)}`; - }); - void boot(); - - function startCollaboration(user) { - document.querySelector("#room").value = roomId; - document.querySelector("#document-title").textContent = roomId; - - const room = new SynixirRoom({ roomId, userId: user.id }); - const status = document.querySelector("#status"); - const connection = document.querySelector("#connection"); - const stopParticipants = showParticipants(room, user.username, presenceOptions); - const destroySurface = createSurface(room); - let unsaved = false; - const saveErrors = { - storage_quota: "This room has reached its storage limit. Keep this tab open or copy your draft, and ask an operator for more space before retrying.", - message_too_large: "This update exceeds the transfer limit. Keep this tab open and reduce the size of your changes before retrying.", - rate_limited: "Too many updates at once. Wait a moment, then disconnect and connect to retry. Keep this tab open.", - invalid_message: "The server rejected this update. Keep this tab open so your draft remains available.", - timeout: "The save acknowledgement did not arrive. Disconnect and connect again to confirm your edits. Keep this tab open.", - }; - function renderSave(value, reason) { - const saveStatus = document.querySelector("#save-status"); - saveStatus.textContent = value; - saveStatus.dataset.state = value === "Saved" ? "saved" : value === "Save failed" ? "failed" : "unsaved"; - unsaved = ["Saving", "Unsaved changes", "Save failed"].includes(value); - document.querySelector("#save-help").textContent = value === "Save failed" - ? saveErrors[reason] ?? "Save could not be confirmed. Disconnect and connect again to retry. Keep this tab open." - : unsaved ? "Keep this tab open until Saved appears. Offline edits stay in this tab until you reconnect." - : "Saved edits stay in this room after everyone leaves."; - } - const saveLabels = { "not-saved": "Not saved", saving: "Saving", saved: "Saved", - unsaved: "Unsaved changes", failed: "Save failed", "view-only": "View only" }; - const connectionLabels = { connected: "Connected", connecting: "Connecting", - reconnecting: "Reconnecting", disconnected: "Disconnected" }; - const connectionErrors = { - node_channel_quota: "The server has reached its connection limit. Keep your draft and try again later.", - room_channel_quota: "This room has reached its participant limit. Keep your draft and try again later.", - account_channel_quota: "Too many room tabs are open for this account. Disconnect another tab before retrying.", - document_quota: "The server has reached its active document limit. Keep your draft and try again later.", - }; - let disposed = false; - let previousRole; - let checkedRevocation = false; - let stopState = () => {}; - stopState = room.subscribe(state => { - if (disposed) return; - renderSave(saveLabels[state.saveStatus], state.saveError); - unsaved = state.hasUnsavedChanges; - destroySurface.setReadOnly(state.readOnly); - document.querySelector("#role-label").textContent = state.role ?? ""; - document.querySelector("#members-panel").hidden = state.role !== "owner"; - if (state.role === "owner" && previousRole !== state.role) void loadMembers(); - previousRole = state.role; - const frozen = ["access_changed", "account_changed"].includes(state.error?.code); - const active = ["connecting", "connected", "reconnecting"].includes(state.connection); - connection.textContent = frozen ? "Reload" : active ? "Disconnect" : "Connect"; - connection.disabled = false; - status.textContent = connectionLabels[state.connection] ?? - (frozen ? "Access changed. Reload to check permissions." - : state.error?.code === "unauthorized" ? "Access expired or denied" - : state.error?.code === "timeout" ? "Connection timed out" - : connectionErrors[state.error?.code] ?? "Document sync failed. Keep this tab open."); - status.dataset.state = state.connection === "error" ? "error" - : state.connection === "connected" ? "connected" : active ? "connecting" : "disconnected"; - if (state.error?.code === "account_changed") { - clearCollaboration(); - window.location.reload(); - } else if (frozen && !checkedRevocation) { - checkedRevocation = true; - void checkRevokedSession(); - } - }); - - // Connection errors are rendered by the subscription. Network failures also - // recover automatically; the button permits explicit disconnect/retry. - function connect() { void room.connect().catch(() => {}); } - connection.addEventListener("click", () => { - if (["access_changed", "account_changed"].includes(room.state.error?.code)) return window.location.reload(); - if (["connecting", "connected", "reconnecting"].includes(room.state.connection)) void room.disconnect(); - else connect(); - }); - - function dispose() { - if (disposed) return; - disposed = true; - stopState(); - stopParticipants(); - destroySurface(); - void room.destroy(); - } - window.addEventListener("pagehide", dispose); - clearCollaboration = () => { - unsaved = false; - dispose(); - document.querySelector("#editor").replaceChildren(); - document.querySelector("#participants").replaceChildren(); - document.querySelector("#collaboration").hidden = true; - }; - hasUnsavedChanges = () => unsaved; - - async function checkRevokedSession() { - try { - const session = await api("/api/session"); - if (session.user?.id !== user.id) { - clearCollaboration(); - window.location.reload(); - } - } catch { /* Keep the revoked draft available to copy. */ } - } - - async function loadMembers() { - try { - const { data } = await api(`/api/rooms/${encodeURIComponent(roomId)}/members`); - document.querySelector("#members-list").replaceChildren(...data.map(member => { - const item = document.createElement("li"); - const label = document.createElement("span"); - label.textContent = member.username; - const select = document.createElement("select"); - select.setAttribute("aria-label", `Role for ${member.username}`); - for (const role of ["owner", "editor", "viewer"]) { - const option = document.createElement("option"); - option.value = role; - option.textContent = role; - select.append(option); - } - select.value = member.role; - select.addEventListener("change", () => changeMember(member.username, select.value)); - const remove = document.createElement("button"); - remove.textContent = "Remove"; - remove.setAttribute("aria-label", `Remove ${member.username}`); - remove.addEventListener("click", () => changeMember(member.username, null)); - item.append(label, select, remove); - return item; - })); - } catch (error) { document.querySelector("#member-error").textContent = explain(error); } - } - async function changeMember(username, nextRole) { - try { - await api(`/api/rooms/${encodeURIComponent(roomId)}/members/${encodeURIComponent(username)}`, { - method: nextRole ? "PUT" : "DELETE", body: nextRole ? { role: nextRole } : undefined, - }); - document.querySelector("#member-error").textContent = ""; - await loadMembers(); - } catch (error) { document.querySelector("#member-error").textContent = explain(error); } - } - document.querySelector("#member-form").addEventListener("submit", event => { - event.preventDefault(); - const values = new FormData(event.currentTarget); - void changeMember(values.get("username"), values.get("role")); - }); - - window.addEventListener("beforeunload", (event) => { - if (unsaved) { - event.preventDefault(); - event.returnValue = ""; - } - }); - - // A page restored from the back/forward cache needs a fresh connection and editor. - window.addEventListener("pageshow", (event) => { - if (event.persisted) window.location.reload(); - }); - - connect(); - } - -} diff --git a/examples/collaboration/flowchart.html b/examples/collaboration/flowchart.html deleted file mode 100644 index 6a72398..0000000 --- a/examples/collaboration/flowchart.html +++ /dev/null @@ -1,101 +0,0 @@ - - - - - - Synixir · Collaborative Flowchart - - - - -
-
- Synixir - -
- - - - - - -
- - - diff --git a/examples/collaboration/flowchart.js b/examples/collaboration/flowchart.js deleted file mode 100644 index 14ed6f5..0000000 --- a/examples/collaboration/flowchart.js +++ /dev/null @@ -1,6 +0,0 @@ -import { startExample } from "./example-shell.js"; -import { createFlowchart } from "./flowchart/canvas.js"; -import "./whiteboard/canvas.css"; -import "./flowchart/canvas.css"; - -startExample({ createSurface: createFlowchart, pagePath: "/flowchart.html", presenceOptions: { avatars: true } }); diff --git a/examples/collaboration/flowchart/canvas.js b/examples/collaboration/flowchart/canvas.ts similarity index 73% rename from examples/collaboration/flowchart/canvas.js rename to examples/collaboration/flowchart/canvas.ts index d23ac18..28f0254 100644 --- a/examples/collaboration/flowchart/canvas.js +++ b/examples/collaboration/flowchart/canvas.ts @@ -1,16 +1,17 @@ -import { createFlowchartModel, canvasSize, colors } from "./model.js"; -import { connectionPath } from "./geometry.js"; -import { createMotion } from "../whiteboard/motion.js"; +import type { SynixirRoom } from "@synixir/client"; +import { createFlowchartModel, canvasSize, colors } from "./model.ts"; +import { connectionPath } from "./geometry.ts"; +import { createMotion } from "../whiteboard/motion.ts"; -export function createFlowchart(room) { - const kindLabel = kind => ({ process: "Process", decision: "Decision", terminal: "Start / End" })[kind]; - const root = document.querySelector("#editor"); - document.querySelector("#open-peer").href = window.location.href; +export function createFlowchart(room: SynixirRoom) { + const kindLabel = (kind: string) => ({ process: "Process", decision: "Decision", terminal: "Start / End" } as Record)[kind] ?? "Node"; + const root = document.querySelector("#editor")!; + document.querySelector("#open-peer")!.href = window.location.href; const model = createFlowchartModel(room.doc); const painted = new Map(); const edgeNodes = new Map(); - let edgeFrame; - const motion = createMotion({ onPaint(node, point) { + let edgeFrame: number|undefined; + const motion = createMotion({ onPaint(node: HTMLElement, point: import("../lib/canvas-data.ts").Point) { if (node.dataset.objectId) { painted.set(node.dataset.objectId, point); // Paint in the same frame as the node so arrows stay attached throughout @@ -19,24 +20,24 @@ export function createFlowchart(room) { } } }); let items = model.list(); - let activityFrame; + let activityFrame: number|undefined; const events = new AbortController(); - const on = (target, event, handler) => target.addEventListener(event, handler, { signal: events.signal }); + const on = (target: EventTarget, event: K, handler: (event: DocumentEventMap[K]) => void, capture = false) => target.addEventListener(event, handler as EventListener, { signal: events.signal, capture }); const nodes = new Map(); const peerNodes = new Map(); let readOnly = true; - let selectedId = null; - let selectedEdgeId = null; + let selectedId: string|null = null; + let selectedEdgeId: string|null = null; let connecting = false; - let drag = null; - let cursor = null; + let drag: { moved: any; id: any; position: any; pointerId: any; start: any; original: any; size: any; }|null = null; + let cursor: { x: number; y: number; }|null = null; let zoom = 1; - let presenceTimer; + let presenceTimer: string|number|NodeJS.Timeout|undefined; let lastPresenceAt = -Infinity; let disposed = false; - const undo = document.querySelector("#undo"); - const redo = document.querySelector("#redo"); - const announce = text => { document.querySelector("#board-announcement").textContent = text; }; + const undo = document.querySelector("#undo")!; + const redo = document.querySelector("#redo")!; + const announce = (text: string|null) => { document.querySelector("#board-announcement")!.textContent = text; }; root.innerHTML = `
@@ -67,21 +68,21 @@ export function createFlowchart(room) {

Connections

Connect two nodes to show what happens next.

    `; - const viewport = root.querySelector("#whiteboard-viewport"); - const sizer = root.querySelector(".canvas-sizer"); - const canvas = root.querySelector(".whiteboard-canvas"); - const layer = root.querySelector(".object-layer"); - const presenceLayer = root.querySelector(".presence-layer"); - const edgeLayer = root.querySelector(".edges"); - const targetInput = root.querySelector("#connection-target"); - const branchInput = root.querySelector("#connection-label"); - const edgeLabel = root.querySelector("#edge-label"); - const connectionList = root.querySelector("#connection-list"); - const svg = tag => document.createElementNS("http://www.w3.org/2000/svg", tag); - const textInput = root.querySelector("#object-text"); - const colorInput = root.querySelector("#object-color"); - const widthInput = root.querySelector("#object-width"); - const heightInput = root.querySelector("#object-height"); + const viewport = root.querySelector("#whiteboard-viewport")!; + const sizer = root.querySelector(".canvas-sizer")!; + const canvas = root.querySelector(".whiteboard-canvas")!; + const layer = root.querySelector(".object-layer")!; + const presenceLayer = root.querySelector(".presence-layer")!; + const edgeLayer = root.querySelector(".edges")!; + const targetInput = root.querySelector("#connection-target")!; + const branchInput = root.querySelector("#connection-label")!; + const edgeLabel = root.querySelector("#edge-label")!; + const connectionList = root.querySelector("#connection-list")!; + const svg = (tag: string) => document.createElementNS("http://www.w3.org/2000/svg", tag); + const textInput = root.querySelector("#object-text")!; + const colorInput = root.querySelector("#object-color")!; + const widthInput = root.querySelector("#object-width")!; + const heightInput = root.querySelector("#object-height")!; canvas.style.width = `${canvasSize.width}px`; canvas.style.height = `${canvasSize.height}px`; for (const color of colors) { @@ -91,9 +92,9 @@ export function createFlowchart(room) { colorInput.append(option); } const selected = () => items.find(item => item.id === selectedId); - const validPoint = point => point && Number.isFinite(point.x) && Number.isFinite(point.y) && + const validPoint = (point: { x: any; y: any; }) => point && Number.isFinite(point.x) && Number.isFinite(point.y) && point.x >= 0 && point.y >= 0 && point.x <= canvasSize.width && point.y <= canvasSize.height; - const worldPoint = event => { + const worldPoint = (event: { clientX: number; clientY: number; }) => { const rect = canvas.getBoundingClientRect(); return { x: (event.clientX - rect.left) / zoom, y: (event.clientY - rect.top) / zoom }; }; @@ -111,10 +112,10 @@ export function createFlowchart(room) { if (remaining <= 0) publish(); else presenceTimer = setTimeout(publish, remaining); } - function select(id) { + function select(id: string|null|undefined) { connecting = false; selectedEdgeId = null; - selectedId = id; + selectedId = id ?? null; model.history.stopCapturing(); render(); publish(); @@ -126,7 +127,7 @@ export function createFlowchart(room) { publish(); scheduleActivity(); } - function setZoom(value) { + function setZoom(value: number) { cancelDrag(); const center = { x: (viewport.scrollLeft + viewport.clientWidth / 2) / zoom, y: (viewport.scrollTop + viewport.clientHeight / 2) / zoom }; @@ -136,16 +137,16 @@ export function createFlowchart(room) { sizer.style.height = `${canvasSize.height * zoom}px`; viewport.scrollLeft = center.x * zoom - viewport.clientWidth / 2; viewport.scrollTop = center.y * zoom - viewport.clientHeight / 2; - root.querySelector("#zoom-level").textContent = `${Math.round(zoom * 100)}%`; - root.querySelector("#zoom-out").disabled = zoom <= 0.5; - root.querySelector("#zoom-in").disabled = zoom >= 1.5; + root.querySelector("#zoom-level")!.textContent = `${Math.round(zoom * 100)}%`; + root.querySelector("#zoom-out")!.disabled = zoom <= 0.5; + root.querySelector("#zoom-in")!.disabled = zoom >= 1.5; cursor = null; publish(); } - on(root.querySelector("#zoom-in"), "click", () => setZoom(zoom + 0.25)); - on(root.querySelector("#zoom-out"), "click", () => setZoom(zoom - 0.25)); - on(root.querySelector("#zoom-reset"), "click", () => setZoom(1)); - root.querySelectorAll("[data-add]").forEach(button => on(button, "click", () => { + on(root.querySelector("#zoom-in")!, "click", () => setZoom(zoom + 0.25)); + on(root.querySelector("#zoom-out")!, "click", () => setZoom(zoom - 0.25)); + on(root.querySelector("#zoom-reset")!, "click", () => setZoom(1)); + root.querySelectorAll("[data-add]").forEach(button => on(button, "click", () => { if (readOnly) return; cancelDrag(); const columns = Math.max(1, Math.floor(viewport.clientWidth / zoom / 260)); @@ -157,20 +158,20 @@ export function createFlowchart(room) { textInput.select(); announce("Node added."); })); - on(viewport, "pointerdown", event => { + on(viewport, "pointerdown", (event) => { if (event.button !== 0 || !event.isPrimary) return; - const edge = event.target.closest("[data-edge-id]"); - if (edge) { selectEdge(edge.dataset.edgeId); return; } - const node = event.target.closest(".whiteboard-object"); + const edge = (event.target as Element).closest("[data-edge-id]"); + if (edge) { selectEdge(edge.dataset.edgeId!); return; } + const node = (event.target as Element).closest(".whiteboard-object"); if (connecting && node && !readOnly) { event.preventDefault(); - completeConnection(node.dataset.objectId); + completeConnection(node.dataset.objectId!); return; } if (!node) { select(null); return; } event.preventDefault(); node.focus({ preventScroll: true }); - select(node.dataset.objectId); + select(node.dataset.objectId!); const item = selected(); if (readOnly || !item) return; const point = worldPoint(event); @@ -178,7 +179,7 @@ export function createFlowchart(room) { position: item.position, size: item.size, moved: false }; viewport.setPointerCapture(event.pointerId); }); - on(viewport, "pointermove", event => { + on(viewport, "pointermove", (event) => { const point = worldPoint(event); cursor = validPoint(point) ? point : null; if (drag && event.pointerId === drag.pointerId && !readOnly) { @@ -189,7 +190,7 @@ export function createFlowchart(room) { } schedulePresence(); }); - on(viewport, "pointerup", event => { + on(viewport, "pointerup", (event) => { if (!drag || event.pointerId !== drag.pointerId) return; const completed = drag; drag = null; @@ -207,14 +208,14 @@ export function createFlowchart(room) { on(viewport, "scroll", () => { cursor = null; schedulePresence(); }); on(window, "blur", () => { cursor = null; cancelDrag(); }); on(document, "visibilitychange", () => { if (document.hidden) { cursor = null; cancelDrag(); } }); - on(viewport, "click", event => { - const node = event.target.closest(".whiteboard-object"); + on(viewport, "click", (event) => { + const node = (event.target as Element).closest(".whiteboard-object"); if (node && event.detail === 0) { - if (connecting && !readOnly) completeConnection(node.dataset.objectId); - else select(node.dataset.objectId); + if (connecting && !readOnly) completeConnection(node.dataset.objectId!); + else select(node.dataset.objectId!); } }); - on(root, "keydown", event => { + on(root, "keydown", (event) => { if (event.key === "Escape") { if (drag) cancelDrag(); else select(null); @@ -228,7 +229,7 @@ export function createFlowchart(room) { if (event.shiftKey) model.history.redo(); else model.history.undo(); return; } - if (event.target !== viewport && !event.target.closest(".whiteboard-object")) return; + if (event.target !== viewport && !(event.target as Element).closest(".whiteboard-object")) return; if (selectedEdgeId && ["Delete", "Backspace"].includes(event.key)) { event.preventDefault(); model.disconnect(selectedEdgeId); return; } @@ -242,7 +243,7 @@ export function createFlowchart(room) { model.move(item.id, { x: item.position.x + delta[0] * step, y: item.position.y + delta[1] * step }); } }); - for (const [input, field] of [[textInput, "text"], [colorInput, "color"]]) { + for (const [input, field] of [[textInput, "text"], [colorInput, "color"]] as const) { on(input, "focus", () => model.history.stopCapturing()); on(input, "blur", () => model.history.stopCapturing()); on(input, "input", () => { if (!readOnly && selectedId) model.edit(selectedId, field, input.value); }); @@ -251,24 +252,24 @@ export function createFlowchart(room) { if (!readOnly && selectedId) model.resize(selectedId, { width: widthInput.valueAsNumber, height: heightInput.valueAsNumber }); renderInspector(); }); - on(root.querySelector("#bring-front"), "click", () => { if (!readOnly && selectedId) model.front(selectedId); }); - on(root.querySelector("#delete-object"), "click", () => { if (!readOnly && selectedId) model.remove(selectedId); }); + on(root.querySelector("#bring-front")!, "click", () => { if (!readOnly && selectedId) model.front(selectedId); }); + on(root.querySelector("#delete-object")!, "click", () => { if (!readOnly && selectedId) model.remove(selectedId); }); on(undo, "click", () => { if (!readOnly) { cancelDrag(); model.history.undo(); } }); on(redo, "click", () => { if (!readOnly) { cancelDrag(); model.history.redo(); } }); - function selectEdge(id) { + function selectEdge(id: string) { select(null); selectedEdgeId = id; render(); } - function completeConnection(target) { + function completeConnection(target: string) { if (readOnly || !selectedId) return; if (target === selectedId) { announce("Choose a different node."); return; } const id = model.connect(selectedId, target, branchInput.value); if (id) { branchInput.value = ""; selectEdge(id); announce("Nodes connected."); } } - on(root.querySelector("#add-connection"), "click", () => completeConnection(targetInput.value)); - on(root.querySelector("#pick-target"), "click", () => { + on(root.querySelector("#add-connection")!, "click", () => completeConnection(targetInput.value)); + on(root.querySelector("#pick-target")!, "click", () => { if (readOnly) return; connecting = !connecting; renderInspector(); @@ -277,16 +278,16 @@ export function createFlowchart(room) { on(edgeLabel, "focus", () => model.history.stopCapturing()); on(edgeLabel, "blur", () => model.history.stopCapturing()); on(edgeLabel, "input", () => { if (!readOnly && selectedEdgeId) model.label(selectedEdgeId, edgeLabel.value); }); - on(root.querySelector("#delete-connection"), "click", () => { if (!readOnly && selectedEdgeId) model.disconnect(selectedEdgeId); }); - on(connectionList, "click", event => { - const button = event.target.closest("[data-edge-id]"); - if (button) selectEdge(button.dataset.edgeId); + on(root.querySelector("#delete-connection")!, "click", () => { if (!readOnly && selectedEdgeId) model.disconnect(selectedEdgeId); }); + on(connectionList, "click", (event) => { + const button = (event.target as Element).closest("[data-edge-id]"); + if (button) selectEdge(button.dataset.edgeId!); }); function scheduleEdges() { if (!disposed && edgeFrame === undefined) edgeFrame = requestAnimationFrame(paintEdges); } function paintEdges() { - cancelAnimationFrame(edgeFrame); + if (edgeFrame !== undefined) cancelAnimationFrame(edgeFrame); edgeFrame = undefined; if (disposed) return; const byId = new Map(items.map(item => [item.id, { ...item, position: painted.get(item.id) ?? item.position }])); @@ -294,7 +295,7 @@ export function createFlowchart(room) { const pair = edgeNodes.get(edge.id); if (!pair) continue; const reverseId = JSON.stringify([edge.target, edge.source]); - const geometry = connectionPath(byId.get(edge.source), byId.get(edge.target), { reciprocal: edgeNodes.has(reverseId) }); + const geometry = connectionPath(byId.get(edge.source)!, byId.get(edge.target)!, { reciprocal: edgeNodes.has(reverseId) }); for (const path of [pair.path, pair.hit]) path.setAttribute("d", geometry.path); pair.label.setAttribute("x", geometry.label.x); pair.label.setAttribute("y", geometry.label.y - 10); @@ -306,7 +307,7 @@ export function createFlowchart(room) { for (const [id, pair] of edgeNodes) if (!present.has(id)) { pair.group.remove(); pair.button.parentElement.remove(); edgeNodes.delete(id); } - const title = id => items.find(item => item.id === id)?.text || "Untitled"; + const title = (id: string) => items.find(item => item.id === id)?.text || "Untitled"; for (const edge of edges) { let pair = edgeNodes.get(edge.id); if (!pair) { @@ -325,7 +326,7 @@ export function createFlowchart(room) { pair.button.textContent = `${title(edge.source)} → ${title(edge.target)}${edge.label ? ` · ${edge.label}` : ""}`; pair.button.setAttribute("aria-pressed", String(selectedEdgeId === edge.id)); } - root.querySelector("#connections-empty").hidden = edges.length > 0; + root.querySelector("#connections-empty")!.hidden = edges.length > 0; scheduleEdges(); } @@ -364,9 +365,9 @@ export function createFlowchart(room) { if (node.firstChild.textContent !== item.text) node.firstChild.textContent = item.text; Object.assign(node.style, { width: `${item.size.width}px`, height: `${item.size.height}px`, zIndex: String(index) }); }); - root.querySelector(".canvas-empty").hidden = items.length > 0; + root.querySelector(".canvas-empty")!.hidden = items.length > 0; const count = `${items.length} ${items.length === 1 ? "node" : "nodes"}`; - if (root.querySelector("#object-count").textContent !== count) root.querySelector("#object-count").textContent = count; + if (root.querySelector("#object-count")!.textContent !== count) root.querySelector("#object-count")!.textContent = count; } function renderActivity() { activityFrame = undefined; @@ -377,8 +378,8 @@ export function createFlowchart(room) { const node = nodes.get(item.id); if (!node) continue; const ownDrag = drag?.id === item.id; - const remoteDrag = remote.findLast(peer => peer.drag?.id === item.id && validPoint(peer.drag))?.drag; - const preview = ownDrag ? drag.position : remoteDrag; + const remoteDrag = remote.findLast((peer: { drag: { id: string; x: number; y: number }; }) => peer.drag?.id === item.id && validPoint(peer.drag))?.drag; + const preview = ownDrag && drag ? drag.position : remoteDrag; const position = preview ? model.boundedPosition(preview, item.size) : item.position; const smooth = !ownDrag && (Boolean(remoteDrag) || motion.moving(node)); motion.move(node, position, smooth); @@ -439,25 +440,25 @@ export function createFlowchart(room) { function renderInspector() { const item = selected(); - root.querySelector("#selection-fields").hidden = !item; - root.querySelector("#selection-empty").hidden = Boolean(item || selectedEdgeId); - root.querySelector("#edge-fields").hidden = !selectedEdgeId; + root.querySelector("#selection-fields")!.hidden = !item; + root.querySelector("#selection-empty")!.hidden = Boolean(item || selectedEdgeId); + root.querySelector("#edge-fields")!.hidden = !selectedEdgeId; const edge = model.connections().find(edge => edge.id === selectedEdgeId); if (edge && edgeLabel.value !== edge.label) edgeLabel.value = edge.label; edgeLabel.readOnly = readOnly; - root.querySelector("#delete-connection").disabled = readOnly; - root.querySelector("#pick-target").textContent = connecting ? "Cancel connection" : "Pick on canvas"; - root.querySelector("#pick-target").setAttribute("aria-pressed", String(connecting)); + root.querySelector("#delete-connection")!.disabled = readOnly; + root.querySelector("#pick-target")!.textContent = connecting ? "Cancel connection" : "Pick on canvas"; + root.querySelector("#pick-target")!.setAttribute("aria-pressed", String(connecting)); viewport.classList.toggle("is-connecting", connecting); - root.querySelector("#canvas-help").textContent = connecting ? "Choose the next node · Escape to cancel" : "Scroll to explore · Select a node to edit or connect"; + root.querySelector("#canvas-help")!.textContent = connecting ? "Choose the next node · Escape to cancel" : "Scroll to explore · Select a node to edit or connect"; if (!item) return; const previousTarget = targetInput.value; targetInput.replaceChildren(...items.filter(node => node.id !== item.id).map(node => { const option = document.createElement("option"); option.value = node.id; option.textContent = node.text || kindLabel(node.kind); return option; })); if ([...targetInput.options].some(option => option.value === previousTarget)) targetInput.value = previousTarget; - for (const input of [targetInput, branchInput, root.querySelector("#add-connection"), root.querySelector("#pick-target")]) input.disabled = readOnly || !targetInput.options.length; - root.querySelector("#selection-title").textContent = kindLabel(item.kind); + for (const input of [targetInput, branchInput, root.querySelector("#add-connection")!, root.querySelector("#pick-target")!]) input.disabled = readOnly || !targetInput.options.length; + root.querySelector("#selection-title")!.textContent = kindLabel(item.kind); if (textInput.value !== item.text) { const start = textInput.selectionStart; const end = textInput.selectionEnd; @@ -465,10 +466,10 @@ export function createFlowchart(room) { if (document.activeElement === textInput) textInput.setSelectionRange(Math.min(start, item.text.length), Math.min(end, item.text.length)); } colorInput.value = item.color; - widthInput.value = item.size.width; - heightInput.value = item.size.height; + widthInput.value = String(item.size.width); + heightInput.value = String(item.size.height); textInput.readOnly = readOnly; - for (const input of [colorInput, widthInput, heightInput, root.querySelector("#bring-front"), root.querySelector("#delete-object")]) input.disabled = readOnly; + for (const input of [colorInput, widthInput, heightInput, root.querySelector("#bring-front")!, root.querySelector("#delete-object")!]) input.disabled = readOnly; } function renderHistory() { undo.disabled = readOnly || !model.history.canUndo(); @@ -495,14 +496,14 @@ export function createFlowchart(room) { model.objects.observeDeep(render); model.edges.observeDeep(render); room.awareness.on("change", scheduleActivity); - for (const event of ["stack-item-added", "stack-item-popped", "stack-cleared", "stack-item-updated"]) model.history.on(event, renderHistory); + for (const event of ["stack-item-added", "stack-item-popped", "stack-cleared", "stack-item-updated"] as const) model.history.on(event, renderHistory); setZoom(1); render(); function destroy() { disposed = true; clearTimeout(presenceTimer); - cancelAnimationFrame(activityFrame); - cancelAnimationFrame(edgeFrame); + if (activityFrame !== undefined) cancelAnimationFrame(activityFrame); + if (edgeFrame !== undefined) cancelAnimationFrame(edgeFrame); motion.destroy(); peerNodes.clear(); events.abort(); @@ -513,11 +514,11 @@ export function createFlowchart(room) { nodes.clear(); root.replaceChildren(); } - destroy.setReadOnly = value => { + destroy.setReadOnly = (value: boolean) => { if (readOnly !== value) { readOnly = value; if (readOnly) { connecting = false; cancelDrag(); } - root.querySelectorAll("[data-add]").forEach(button => { button.disabled = readOnly; }); + root.querySelectorAll("[data-add]").forEach(button => { button.disabled = readOnly; }); render(); } scheduleActivity(); diff --git a/examples/collaboration/flowchart/geometry.js b/examples/collaboration/flowchart/geometry.ts similarity index 84% rename from examples/collaboration/flowchart/geometry.js rename to examples/collaboration/flowchart/geometry.ts index ea6bed3..2723034 100644 --- a/examples/collaboration/flowchart/geometry.js +++ b/examples/collaboration/flowchart/geometry.ts @@ -1,6 +1,6 @@ // Attach arrows to the side facing the other node. Control points keep the // arrow perpendicular to the boundary, including the tips of decision nodes. -export function connectionPath(source, target, { reciprocal = false } = {}) { +export function connectionPath(source: Pick, target: Pick, { reciprocal = false } = {}) { const a = { x: source.position.x + source.size.width / 2, y: source.position.y + source.size.height / 2 }; const b = { x: target.position.x + target.size.width / 2, y: target.position.y + target.size.height / 2 }; const horizontal = Math.abs(b.x - a.x) > Math.abs(b.y - a.y); diff --git a/examples/collaboration/flowchart/model.test.js b/examples/collaboration/flowchart/model.test.ts similarity index 94% rename from examples/collaboration/flowchart/model.test.js rename to examples/collaboration/flowchart/model.test.ts index 46e9f55..3a3cdbf 100644 --- a/examples/collaboration/flowchart/model.test.js +++ b/examples/collaboration/flowchart/model.test.ts @@ -1,7 +1,7 @@ import { test } from "node:test"; import assert from "node:assert/strict"; import * as Y from "yjs"; -import { createFlowchartModel } from "./model.js"; +import { createFlowchartModel } from "./model.ts"; function clients() { const a = new Y.Doc(), b = new Y.Doc(); @@ -26,7 +26,7 @@ test("concurrent connections converge while local undo preserves another user's first.move(start, { x: 320, y: 400 }); second.label(edge, "Approved"); sync(); first.history.undo(); sync(); - assert.deepEqual(first.list().find(n => n.id === start).position, { x: 80, y: 80 }); + assert.deepEqual(first.list().find(n => n.id === start)!.position, { x: 80, y: 80 }); assert.equal(first.connections()[0].label, "Approved"); } finally { close(); } }); diff --git a/examples/collaboration/flowchart/model.js b/examples/collaboration/flowchart/model.ts similarity index 67% rename from examples/collaboration/flowchart/model.js rename to examples/collaboration/flowchart/model.ts index c884274..6669bff 100644 --- a/examples/collaboration/flowchart/model.js +++ b/examples/collaboration/flowchart/model.ts @@ -1,31 +1,26 @@ import * as Y from "yjs"; +import { canvasItem, type Point, type Size } from "../lib/canvas-data.ts"; export const canvasSize = { width: 1600, height: 1000 }; export const kinds = ["process", "decision", "terminal"]; export const colors = ["yellow", "blue", "mint", "rose", "white"]; -const clamp = (value, min, max) => Math.min(max, Math.max(min, value)); +const clamp = (value: number, min: number, max: number) => Math.min(max, Math.max(min, value)); -export function createFlowchartModel(doc) { +export function createFlowchartModel(doc: Y.Doc) { const objects = doc.getMap("flowchart:nodes:v1"); const edges = doc.getMap("flowchart:edges:v1"); const origin = {}; const history = new Y.UndoManager([objects, edges], { trackedOrigins: new Set([origin]) }); function list() { - return [...objects.entries()].filter(([, item]) => item instanceof Y.Map).map(([id, item]) => ({ - id, kind: item.get("kind"), text: String(item.get("text") ?? ""), - color: colors.includes(item.get("color")) ? item.get("color") : "yellow", - position: item.get("position"), size: item.get("size"), order: item.get("order"), - })).filter(item => kinds.includes(item.kind) && Number.isFinite(item.order) && - Number.isFinite(item.position?.x) && Number.isFinite(item.position?.y) && - Number.isFinite(item.size?.width) && Number.isFinite(item.size?.height) && - item.size.width >= 80 && item.size.width <= 600 && item.size.height >= 60 && item.size.height <= 500) + return [...objects.entries()].map(([id, value]) => canvasItem(id, value, kinds, colors)) + .filter(item => item !== undefined) .map(item => ({ ...item, position: boundedPosition(item.position, item.size) })) .sort((a, b) => a.order - b.order || a.id.localeCompare(b.id)); } - function boundedPosition(position, size) { + function boundedPosition(position: Point, size: Size) { return { x: clamp(position.x, 0, canvasSize.width - size.width), y: clamp(position.y, 0, canvasSize.height - size.height) }; } - function action(fn) { + function action(fn: () => void) { history.stopCapturing(); doc.transact(fn, origin); history.stopCapturing(); @@ -34,16 +29,16 @@ export function createFlowchartModel(doc) { // undo can restore it, but only expose edges whose endpoints still exist. function connections() { const ids = new Set(list().map(item => item.id)); - return [...edges.entries()].filter(([, edge]) => edge instanceof Y.Map) - .map(([id, edge]) => ({ id, source: edge.get("source"), target: edge.get("target"), label: String(edge.get("label") ?? "").slice(0, 80) })) + return [...edges.entries()].filter((entry): entry is [string, Y.Map] => entry[1] instanceof Y.Map) + .map(([id, edge]) => ({ id, source: String(edge.get("source") ?? ""), target: String(edge.get("target") ?? ""), label: String(edge.get("label") ?? "").slice(0, 80) })) .filter(edge => ids.has(edge.source) && ids.has(edge.target) && edge.source !== edge.target) .sort((a, b) => a.id.localeCompare(b.id)); } const nextOrder = () => list().reduce((max, item) => Math.max(max, item.order), 0) + 1; return { objects, edges, history, list, connections, boundedPosition, - add(kind, position = { x: 80, y: 80 }) { - if (!kinds.includes(kind) || !Number.isFinite(position.x) || !Number.isFinite(position.y)) return; + add(kind: string|undefined, position = { x: 80, y: 80 }) { + if (!kind || !kinds.includes(kind) || !Number.isFinite(position.x) || !Number.isFinite(position.y)) return; const id = crypto.randomUUID(); const size = kind === "decision" ? { width: 200, height: 160 } : { width: 200, height: 90 }; action(() => objects.set(id, new Y.Map([ @@ -53,49 +48,50 @@ export function createFlowchartModel(doc) { ]))); return id; }, - move(id, position) { - const item = objects.get(id); + move(id: string|undefined, position: Point) { + const item = objects.get(id ?? ""); if (!(item instanceof Y.Map) || !Number.isFinite(position.x) || !Number.isFinite(position.y)) return; const next = boundedPosition(position, item.get("size")); const previous = item.get("position"); if (previous.x === next.x && previous.y === next.y) return; action(() => item.set("position", next)); }, - resize(id, size) { - const item = objects.get(id); + resize(id: string, size: Size) { + const item = objects.get(id ?? ""); if (!(item instanceof Y.Map) || !Number.isFinite(size.width) || !Number.isFinite(size.height)) return; const position = item.get("position"); const next = { width: clamp(size.width, 80, Math.min(600, canvasSize.width - position.x)), height: clamp(size.height, 60, Math.min(500, canvasSize.height - position.y)) }; action(() => item.set("size", next)); }, - edit(id, field, value) { - const item = objects.get(id); + edit(id: string, field: string, value: string) { + const item = objects.get(id ?? ""); if (!(item instanceof Y.Map) || !["text", "color"].includes(field) || typeof value !== "string") return; if (field === "color" && !colors.includes(value)) return; if (item.get(field) === value) return; doc.transact(() => item.set(field, field === "text" ? value.slice(0, 2000) : value), origin); }, - front(id) { - if (objects.get(id) instanceof Y.Map) action(() => objects.get(id).set("order", nextOrder())); + front(id: string) { + const item = objects.get(id); + if (item instanceof Y.Map) action(() => item.set("order", nextOrder())); }, - connect(source, target, label = "") { - if (source === target || !objects.has(source) || !objects.has(target) || typeof label !== "string") return; + connect(source: string|undefined, target: string|undefined, label = "") { + if (!source || !target || source === target || !objects.has(source) || !objects.has(target) || typeof label !== "string") return; // A deterministic key collapses concurrent creation of the same arrow. const id = JSON.stringify([source, target]); if (edges.has(id)) return id; action(() => edges.set(id, new Y.Map([["source", source], ["target", target], ["label", label.slice(0, 80)]]))); return id; }, - label(id, value) { + label(id: string, value: string) { const edge = edges.get(id); if (edge instanceof Y.Map && typeof value === "string" && edge.get("label") !== value) doc.transact(() => edge.set("label", value.slice(0, 80)), origin); }, - disconnect(id) { action(() => edges.delete(id)); }, - remove(id) { + disconnect(id: string) { action(() => edges.delete(id)); }, + remove(id: string|undefined) { action(() => { - objects.delete(id); + objects.delete(id ?? ""); for (const [edgeId, edge] of edges) { if (edge instanceof Y.Map && (edge.get("source") === id || edge.get("target") === id)) edges.delete(edgeId); } diff --git a/examples/collaboration/index.html b/examples/collaboration/index.html deleted file mode 100644 index 11fefd6..0000000 --- a/examples/collaboration/index.html +++ /dev/null @@ -1,98 +0,0 @@ - - - - - - Synixir · Shared document - - - -
    -
    - Synixir - -
    - - - - - - -
    - - - diff --git a/examples/collaboration/kanban.html b/examples/collaboration/kanban.html deleted file mode 100644 index 7ab089e..0000000 --- a/examples/collaboration/kanban.html +++ /dev/null @@ -1,101 +0,0 @@ - - - - - - Synixir · Collaborative Kanban - - - - -
    -
    - Synixir - -
    - - - - - - -
    - - - diff --git a/examples/collaboration/kanban.js b/examples/collaboration/kanban.js deleted file mode 100644 index 4c1d9ee..0000000 --- a/examples/collaboration/kanban.js +++ /dev/null @@ -1,5 +0,0 @@ -import { startExample } from "./example-shell.js"; -import { createBoard } from "./kanban/board.js"; -import "./kanban/board.css"; - -startExample({ createSurface: createBoard, pagePath: "/kanban.html", presenceOptions: { avatars: true } }); diff --git a/examples/collaboration/kanban/board.js b/examples/collaboration/kanban/board.ts similarity index 73% rename from examples/collaboration/kanban/board.js rename to examples/collaboration/kanban/board.ts index 8ea07eb..9174116 100644 --- a/examples/collaboration/kanban/board.js +++ b/examples/collaboration/kanban/board.ts @@ -1,22 +1,23 @@ -import { createBoardModel, columns, colors } from "./model.js"; +import type { SynixirRoom } from "@synixir/client"; +import { createBoardModel, columns, colors } from "./model.ts"; -export function createBoard(room) { - const root = document.querySelector("#editor"); - document.querySelector("#open-peer").href = window.location.href; +export function createBoard(room: SynixirRoom) { + const root = document.querySelector("#editor")!; + document.querySelector("#open-peer")!.href = window.location.href; const model = createBoardModel(room.doc); const events = new AbortController(); - const listen = (target, event, fn) => target.addEventListener(event, fn, { signal: events.signal }); + const listen = (target: EventTarget, event: K, handler: (event: DocumentEventMap[K]) => void, capture = false) => target.addEventListener(event, handler as EventListener, { signal: events.signal, capture }); let readOnly = true; - let selectedId = null; - let returnFocusId = null; - let draggedId = null; + let selectedId: string|null = null; + let returnFocusId: string|null|undefined = null; + let draggedId: string|null = null; let disposed = false; const nodes = new Map(); - const undo = document.querySelector("#undo"); - const redo = document.querySelector("#redo"); - const announce = text => { document.querySelector("#board-announcement").textContent = text; }; + const undo = document.querySelector("#undo")!; + const redo = document.querySelector("#redo")!; + const announce = (text: string|null) => { document.querySelector("#board-announcement")!.textContent = text; }; - function element(tag, className, text) { + function element(tag: K, className: string, text?: string) { const node = document.createElement(tag); node.className = className; if (text !== undefined) node.textContent = text; @@ -44,16 +45,16 @@ export function createBoard(room) { openCard(model.add(column.id)); titleInput.select(); }); - listen(section, "dragover", event => { + listen(section, "dragover", (event) => { if (readOnly || !draggedId) return; event.preventDefault(); - event.dataTransfer.dropEffect = "move"; + event.dataTransfer!.dropEffect = "move"; section.classList.add("drop-target"); }); - listen(section, "dragleave", event => { - if (!section.contains(event.relatedTarget)) section.classList.remove("drop-target"); + listen(section, "dragleave", (event) => { + if (!section.contains(event.relatedTarget as Node | null)) section.classList.remove("drop-target"); }); - listen(section, "drop", event => { + listen(section, "drop", (event) => { event.preventDefault(); if (!readOnly && draggedId) { model.move(draggedId, column.id); @@ -73,11 +74,11 @@ export function createBoard(room) {
    `; root.replaceChildren(board, dialog); - const titleInput = dialog.querySelector("#card-title"); - const descriptionInput = dialog.querySelector("#card-description"); - const columnInput = dialog.querySelector("#card-column"); - const colorInput = dialog.querySelector("#card-color"); - const deleteButton = dialog.querySelector("#delete-card"); + const titleInput = dialog.querySelector("#card-title")!; + const descriptionInput = dialog.querySelector("#card-description")!; + const columnInput = dialog.querySelector("#card-column")!; + const colorInput = dialog.querySelector("#card-color")!; + const deleteButton = dialog.querySelector("#delete-card")!; for (const column of columns) { const option = element("option", "", column.label); option.value = column.id; @@ -92,9 +93,9 @@ export function createBoard(room) { function publish(action = "viewing", cardId = selectedId) { room.awareness.setLocalStateField("kanban", cardId ? { cardId, action } : null); } - function openCard(id) { + function openCard(id: string|undefined) { if (!id || !model.cards.has(id)) return; - selectedId = id; + selectedId = id ?? null; returnFocusId = id; model.history.stopCapturing(); renderDialog(); @@ -109,18 +110,18 @@ export function createBoard(room) { publish(); render(); } - listen(dialog.querySelector("#close-card"), "click", () => dialog.close()); + listen(dialog.querySelector("#close-card")!, "click", () => dialog.close()); listen(dialog, "close", () => { selectedId = null; model.history.stopCapturing(); publish(); - const opener = [...root.querySelectorAll(".kanban-card")] - .find(node => node.dataset.cardId === returnFocusId)?.querySelector(".card-open"); - const fallback = !undo.disabled ? undo : root.querySelector(".add-card:not(:disabled)") ?? document.querySelector("#connection"); + const opener = [...root.querySelectorAll(".kanban-card")] + .find(node => node.dataset.cardId === returnFocusId)?.querySelector(".card-open")!; + const fallback = !undo.disabled ? undo : root.querySelector(".add-card:not(:disabled)")! ?? document.querySelector("#connection")!; (opener ?? fallback).focus(); returnFocusId = null; }); - for (const [input, field] of [[titleInput, "title"], [descriptionInput, "description"], [colorInput, "color"]]) { + for (const [input, field] of [[titleInput, "title"], [descriptionInput, "description"], [colorInput, "color"]] as const) { listen(input, "focus", () => { model.history.stopCapturing(); publish(readOnly ? "viewing" : "editing"); }); listen(input, "blur", () => { model.history.stopCapturing(); publish(); }); listen(input, "input", () => { @@ -138,7 +139,7 @@ export function createBoard(room) { }); listen(undo, "click", () => { if (!readOnly) model.history.undo(); }); listen(redo, "click", () => { if (!readOnly) model.history.redo(); }); - listen(dialog, "keydown", event => { + listen(dialog, "keydown", (event) => { if (readOnly || event.isComposing || !(event.ctrlKey || event.metaKey) || event.altKey || event.key.toLowerCase() !== "z") return; event.preventDefault(); if (event.shiftKey) model.history.redo(); @@ -147,7 +148,7 @@ export function createBoard(room) { // Remote field replacements keep the caret near its original text. Edits to // separate fields merge; simultaneous edits to the same field are Y.Map LWW. - function syncInput(input, value) { + function syncInput(input: HTMLInputElement | HTMLTextAreaElement, value: string) { if (input.value === value) return; const before = input.value; const start = input.selectionStart; @@ -159,9 +160,9 @@ export function createBoard(room) { before[before.length - 1 - suffix] === value[value.length - 1 - suffix]) suffix++; input.value = value; if (document.activeElement === input && start !== null) { - const translate = position => position <= prefix ? position : position >= before.length - suffix + const translate = (position: number) => position <= prefix ? position : position >= before.length - suffix ? position + value.length - before.length : value.length - suffix; - input.setSelectionRange(translate(start), translate(end)); + input.setSelectionRange(translate(start), translate(end ?? start)); } } function renderDialog() { @@ -180,9 +181,9 @@ export function createBoard(room) { colorInput.value = card.color; titleInput.readOnly = descriptionInput.readOnly = readOnly; columnInput.disabled = colorInput.disabled = deleteButton.disabled = readOnly; - dialog.querySelector("#card-edit-help").textContent = readOnly ? "View only" : "Changes save as you type."; + dialog.querySelector("#card-edit-help")!.textContent = readOnly ? "View only" : "Changes save as you type."; } - function collaborators(id) { + function collaborators(id: string|undefined) { if (room.state.connection !== "connected") return []; return [...room.awareness.getStates()].filter(([client, state]) => client !== room.awareness.clientID && state.kanban?.cardId === id) .map(([, state]) => ({ name: typeof state.user?.name === "string" ? state.user.name.slice(0, 32) : "Guest", @@ -190,12 +191,12 @@ export function createBoard(room) { } function renderPresence() { if (disposed) return; - root.querySelectorAll(".kanban-card").forEach(node => { + root.querySelectorAll(".kanban-card").forEach(node => { const people = collaborators(node.dataset.cardId); - node.querySelector(".card-presence").textContent = people.map(person => `${person.name} ${person.action}`).join(" · "); + node.querySelector(".card-presence")!.textContent = people.map(person => `${person.name} ${person.action}`).join(" · "); node.classList.toggle("has-presence", people.length > 0); }); - dialog.querySelector("#card-collaborators").textContent = selectedId + dialog.querySelector("#card-collaborators")!.textContent = selectedId ? collaborators(selectedId).map(person => `${person.name} is ${person.action} this card`).join(" · ") : ""; } function renderHistory() { @@ -206,7 +207,7 @@ export function createBoard(room) { // Keep the browser's dragged element mounted while remote changes arrive. // The model still syncs immediately; endDrag renders the latest state. if (disposed || draggedId) return; - const focusedId = document.activeElement?.closest(".kanban-card")?.dataset.cardId; + const focusedId = document.activeElement?.closest(".kanban-card")?.dataset.cardId; const cards = model.list(); for (const column of columns) { const { list, count, add } = nodes.get(column.id); @@ -235,8 +236,8 @@ export function createBoard(room) { node.addEventListener("dragstart", event => { if (readOnly) return event.preventDefault(); draggedId = card.id; - event.dataTransfer.setData("text/plain", card.id); - event.dataTransfer.effectAllowed = "move"; + event.dataTransfer!.setData("text/plain", card.id); + event.dataTransfer!.effectAllowed = "move"; publish("moving", card.id); }); node.addEventListener("dragend", endDrag); @@ -245,7 +246,7 @@ export function createBoard(room) { if (!items.length) list.append(element("p", "column-empty", column.id === "done" ? "Finished work lands here." : "No cards yet.")); } if (focusedId) { - [...root.querySelectorAll(".kanban-card")].find(node => node.dataset.cardId === focusedId)?.querySelector("button").focus(); + [...root.querySelectorAll(".kanban-card")].find(node => node.dataset.cardId === focusedId)?.querySelector("button")!.focus(); } renderDialog(); renderPresence(); @@ -253,7 +254,7 @@ export function createBoard(room) { } model.cards.observeDeep(render); room.awareness.on("change", renderPresence); - for (const event of ["stack-item-added", "stack-item-popped", "stack-cleared", "stack-item-updated"]) model.history.on(event, renderHistory); + for (const event of ["stack-item-added", "stack-item-popped", "stack-cleared", "stack-item-updated"] as const) model.history.on(event, renderHistory); render(); function destroy() { @@ -265,7 +266,7 @@ export function createBoard(room) { dialog.close(); root.replaceChildren(); } - destroy.setReadOnly = value => { + destroy.setReadOnly = (value: boolean) => { if (readOnly !== value) { readOnly = value; if (readOnly) endDrag(); diff --git a/examples/collaboration/kanban/model.test.js b/examples/collaboration/kanban/model.test.ts similarity index 98% rename from examples/collaboration/kanban/model.test.js rename to examples/collaboration/kanban/model.test.ts index d815b3b..0266fa0 100644 --- a/examples/collaboration/kanban/model.test.js +++ b/examples/collaboration/kanban/model.test.ts @@ -1,7 +1,7 @@ import { test } from "node:test"; import assert from "node:assert/strict"; import * as Y from "yjs"; -import { createBoardModel } from "./model.js"; +import { createBoardModel } from "./model.ts"; function clients() { const a = new Y.Doc(); diff --git a/examples/collaboration/kanban/model.js b/examples/collaboration/kanban/model.ts similarity index 60% rename from examples/collaboration/kanban/model.js rename to examples/collaboration/kanban/model.ts index 3cbebb8..70c4a67 100644 --- a/examples/collaboration/kanban/model.js +++ b/examples/collaboration/kanban/model.ts @@ -9,29 +9,30 @@ export const colors = ["neutral", "blue", "green", "amber", "rose"]; // One map entry per card. Column and order change together, so concurrent // moves choose one placement instead of inserting the card into two lists. -export function createBoardModel(doc) { +export function createBoardModel(doc: Y.Doc) { const cards = doc.getMap("kanban:cards:v1"); const origin = {}; const history = new Y.UndoManager(cards, { trackedOrigins: new Set([origin]) }); - const validColumn = id => columns.some(column => column.id === id); - const list = () => [...cards.entries()] - .filter(([, card]) => card instanceof Y.Map) + const validColumn = (id: string) => columns.some(column => column.id === id); + interface Card { id: string; title: string; description: string; color: string; placement: { column: string; order: number } } + const list = (): Card[] => [...cards.entries()] + .filter((entry): entry is [string, Y.Map] => entry[1] instanceof Y.Map) .map(([id, card]) => ({ id, title: String(card.get("title") ?? ""), description: String(card.get("description") ?? ""), - color: colors.includes(card.get("color")) ? card.get("color") : "neutral", - placement: card.get("placement") })) - .filter(card => validColumn(card.placement?.column) && Number.isFinite(card.placement?.order)) + color: colors.includes(String(card.get("color"))) ? String(card.get("color")) : "neutral", + placement: card.get("placement") as Card["placement"] | undefined })) + .filter((card): card is Card => !!card.placement && validColumn(card.placement.column) && Number.isFinite(card.placement?.order)) .sort((a, b) => a.placement.order - b.placement.order || a.id.localeCompare(b.id)); - const nextOrder = column => Math.max(0, ...list().filter(card => card.placement.column === column) + const nextOrder = (column: string) => Math.max(0, ...list().filter(card => card.placement.column === column) .map(card => card.placement.order)) + 1; - function action(fn) { + function action(fn: () => void) { history.stopCapturing(); doc.transact(fn, origin); history.stopCapturing(); } return { cards, history, list, - add(column) { + add(column: string) { if (!validColumn(column)) return; const id = crypto.randomUUID(); action(() => cards.set(id, new Y.Map([ @@ -40,19 +41,19 @@ export function createBoardModel(doc) { ]))); return id; }, - edit(id, field, value) { - const card = cards.get(id); + edit(id: string|undefined, field: string, value: string) { + const card = cards.get(id ?? ""); if (!(card instanceof Y.Map) || !["title", "description", "color"].includes(field)) return; if (field === "color" && !colors.includes(value)) return; if (card.get(field) === value) return; doc.transact(() => card.set(field, value), origin); }, - move(id, column) { - const card = cards.get(id); + move(id: string|undefined, column: string) { + const card = cards.get(id ?? ""); if (!(card instanceof Y.Map) || !validColumn(column) || card.get("placement")?.column === column) return; action(() => card.set("placement", { column, order: nextOrder(column) })); }, - remove(id) { action(() => cards.delete(id)); }, + remove(id: string|undefined) { action(() => cards.delete(id ?? "")); }, destroy() { history.destroy(); }, }; } diff --git a/examples/collaboration/lib/api.ts b/examples/collaboration/lib/api.ts new file mode 100644 index 0000000..3a152c3 --- /dev/null +++ b/examples/collaboration/lib/api.ts @@ -0,0 +1,62 @@ +export interface User { + id: string; + username: string; +} +export interface Session { + user: User | null; + csrf_token: string; +} +export interface Member { + username: string; + role: "owner" | "editor" | "viewer"; +} +export interface RoomSummary { + id: string; + role: string; +} +export async function api( + path: string, + method = "GET", + body?: unknown, + signal?: AbortSignal, +): Promise { + const headers: Record = { "Content-Type": "application/json" }; + if (method !== "GET") { + const session = await api("/api/session", "GET", undefined, signal); + headers["x-csrf-token"] = session.csrf_token; + } + const response = await fetch(path, { + method, + headers, + credentials: "same-origin", + signal: signal ?? AbortSignal.timeout(10_000), + body: body === undefined ? undefined : JSON.stringify(body), + }); + const result = await response.json(); + if (!response.ok) throw new Error(result.error ?? "request_failed"); + return result as T; +} +const messages: Record = { + invalid_credentials: "Username or password is incorrect.", + invalid_registration: "Choose an available username and a password of 15–128 characters.", + rate_limited: "Too many attempts. Wait a minute and try again.", + unauthorized: "Access expired or denied", + forbidden: "Only an owner can manage room access.", + last_owner: "Add another owner before removing or changing the last owner.", + room_unavailable: "That room ID is unavailable. Choose another.", + room_quota: + "This account has reached its room ownership limit. Ask an operator to review the limit.", + account_not_found: "No account has that username.", +}; +export function explain(error: unknown) { + return ( + messages[error instanceof Error ? error.message : ""] ?? "The request failed. Please try again." + ); +} +export function sessionChanged() { + try { + localStorage.setItem("synixir:session-change", crypto.randomUUID()); + } catch { + /* Storage may be disabled. */ + } +} diff --git a/examples/collaboration/lib/browser-test.ts b/examples/collaboration/lib/browser-test.ts new file mode 100644 index 0000000..c8ea89f --- /dev/null +++ b/examples/collaboration/lib/browser-test.ts @@ -0,0 +1,15 @@ +import type { SynixirRoom } from "@synixir/client"; +import type { EditorView } from "@codemirror/view"; +declare global { + interface Window { + synixirTest: { room?: SynixirRoom; editor?: EditorView }; + } +} +// Compiled out of production builds. Browser tests inspect public SDK/editor APIs. +export function inspectForTest(values: Window["synixirTest"]) { + if ( + process.env.NODE_ENV === "development" && + process.env.NEXT_PUBLIC_SYNIXIR_BROWSER_TEST === "true" + ) + window.synixirTest = { ...window.synixirTest, ...values }; +} diff --git a/examples/collaboration/lib/canvas-data.ts b/examples/collaboration/lib/canvas-data.ts new file mode 100644 index 0000000..94d4a8b --- /dev/null +++ b/examples/collaboration/lib/canvas-data.ts @@ -0,0 +1,73 @@ +import * as Y from "yjs"; +export interface Point { + x: number; + y: number; +} +export interface Size { + width: number; + height: number; +} +export interface CanvasItem { + id: string; + kind: string; + text: string; + color: string; + position: Point; + size: Size; + order: number; +} +export function canvasItem( + id: string, + value: unknown, + kinds: string[], + colors: string[], +): CanvasItem | undefined { + if (!(value instanceof Y.Map)) return; + const kind: unknown = value.get("kind"), + order: unknown = value.get("order"); + const position: unknown = value.get("position"), + size: unknown = value.get("size"); + if ( + typeof kind !== "string" || + !kinds.includes(kind) || + typeof order !== "number" || + !Number.isFinite(order) + ) + return; + if ( + !position || + typeof position !== "object" || + !("x" in position) || + !("y" in position) || + typeof position.x !== "number" || + typeof position.y !== "number" || + !Number.isFinite(position.x) || + !Number.isFinite(position.y) + ) + return; + if ( + !size || + typeof size !== "object" || + !("width" in size) || + !("height" in size) || + typeof size.width !== "number" || + typeof size.height !== "number" || + !Number.isFinite(size.width) || + !Number.isFinite(size.height) || + size.width < 80 || + size.width > 600 || + size.height < 60 || + size.height > 500 + ) + return; + const color: unknown = value.get("color"); + return { + id, + kind, + order, + text: String(value.get("text") ?? ""), + color: typeof color === "string" && colors.includes(color) ? color : "yellow", + position: { x: position.x, y: position.y }, + size: { width: size.width, height: size.height }, + }; +} diff --git a/examples/collaboration/lib/examples.ts b/examples/collaboration/lib/examples.ts new file mode 100644 index 0000000..117bccd --- /dev/null +++ b/examples/collaboration/lib/examples.ts @@ -0,0 +1,80 @@ +import { + FileText, + Columns3, + PencilRuler, + Type, + Workflow, + Table2, + ListChecks, + SlidersHorizontal, +} from "lucide-react"; +export const examples = [ + { + id: "text", + path: "/", + title: "Text editor", + description: "Write together, down to the last character.", + icon: FileText, + hint: "Live cursors and shared undo history", + }, + { + id: "kanban", + path: "/kanban.html", + title: "Kanban board", + description: "Move work forward with your whole team.", + icon: Columns3, + hint: "Drag cards between columns", + }, + { + id: "whiteboard", + path: "/whiteboard.html", + title: "Whiteboard", + description: "A shared canvas for your next idea.", + icon: PencilRuler, + hint: "Drag objects · Arrow keys to move", + }, + { + id: "rich-text", + path: "/rich-text.html", + title: "Rich text", + description: "Turn a rough outline into something worth sharing.", + icon: Type, + hint: "Formatting, live cursors, and shared history", + }, + { + id: "multiplayer-form", + path: "/multiplayer-form.html", + title: "Project brief", + description: "Work through the details, together.", + icon: ListChecks, + hint: "Shared fields with live editing presence", + }, + { + id: "flowchart", + path: "/flowchart.html", + title: "Flowchart", + description: "Connect the dots in your team's thinking.", + icon: Workflow, + hint: "Connect steps and label your decisions", + }, + { + id: "table", + path: "/table.html", + title: "Table", + description: "Give your team's information a shared home.", + icon: Table2, + hint: "Edit cells together or paste from a spreadsheet", + }, + { + id: "sdk", + path: "/sdk.html", + title: "Shared settings", + description: "One small setting. Every client in sync.", + icon: SlidersHorizontal, + hint: "A minimal client SDK example", + }, +] as const; +export type ExampleKind = (typeof examples)[number]["id"]; +export function example(kind: ExampleKind) { + return examples.find((item) => item.id === kind)!; +} diff --git a/examples/collaboration/lib/utils.ts b/examples/collaboration/lib/utils.ts new file mode 100644 index 0000000..fe0d169 --- /dev/null +++ b/examples/collaboration/lib/utils.ts @@ -0,0 +1 @@ +export { cn } from "cn"; diff --git a/examples/collaboration/main.js b/examples/collaboration/main.js deleted file mode 100644 index b7100c4..0000000 --- a/examples/collaboration/main.js +++ /dev/null @@ -1,4 +0,0 @@ -import { createEditor } from "./editor.js"; -import { startExample } from "./example-shell.js"; - -startExample({ createSurface: room => createEditor(room.doc.getText("content"), room.awareness) }); diff --git a/examples/collaboration/multiplayer-form.html b/examples/collaboration/multiplayer-form.html deleted file mode 100644 index 4203da5..0000000 --- a/examples/collaboration/multiplayer-form.html +++ /dev/null @@ -1,99 +0,0 @@ - - - - - - Synixir · Multiplayer form - - - -
    -
    - Synixir - -
    - - - - - - -
    - - - diff --git a/examples/collaboration/multiplayer-form.js b/examples/collaboration/multiplayer-form.js deleted file mode 100644 index 7b0ef4b..0000000 --- a/examples/collaboration/multiplayer-form.js +++ /dev/null @@ -1,5 +0,0 @@ -import { startExample } from "./example-shell.js"; -import { createMultiplayerForm } from "./multiplayer-form/form.js"; -import "./multiplayer-form/form.css"; - -startExample({ createSurface: createMultiplayerForm, pagePath: "/multiplayer-form.html", presenceOptions: { avatars: true } }); diff --git a/examples/collaboration/multiplayer-form/form.js b/examples/collaboration/multiplayer-form/form.ts similarity index 76% rename from examples/collaboration/multiplayer-form/form.js rename to examples/collaboration/multiplayer-form/form.ts index 8ea4d3f..500a9f2 100644 --- a/examples/collaboration/multiplayer-form/form.js +++ b/examples/collaboration/multiplayer-form/form.ts @@ -1,20 +1,21 @@ +import type { SynixirRoom } from "@synixir/client"; import { EditorView, drawSelection, keymap, placeholder } from "@codemirror/view"; import { Compartment, EditorState, Prec } from "@codemirror/state"; import { defaultKeymap } from "@codemirror/commands"; import { yCollab, yUndoManagerKeymap } from "y-codemirror.next"; -import { createFormModel, textFields, teams, priorities, channels, requiredFields, validate } from "./model.js"; +import { createFormModel, textFields, teams, priorities, channels, requiredFields, validate } from "./model.ts"; -export function createMultiplayerForm(room) { - const root = document.querySelector("#editor"); - document.querySelector("#open-peer").href = window.location.href; +export function createMultiplayerForm(room: SynixirRoom) { + const root = document.querySelector("#editor")!; + document.querySelector("#open-peer")!.href = window.location.href; const model = createFormModel(room.doc); const events = new AbortController(); - const on = (node, event, handler) => node.addEventListener(event, handler, { signal: events.signal }); + const on = (target: EventTarget, event: K, handler: (event: DocumentEventMap[K]) => void, capture = false) => target.addEventListener(event, handler as EventListener, { signal: events.signal, capture }); const editors = new Map(); const fields = new Map(); const touched = new Set(); let readOnly = true; - let activeField = null; + let activeField: string | null = null; let disposed = false; root.innerHTML = ` @@ -28,17 +29,17 @@ export function createMultiplayerForm(room) {

    LIVE PREVIEW

    Project brief

    This preview updates as your team edits. It does not submit the form.

    `; - const form = root.querySelector("form"); - const dialog = root.querySelector("dialog"); - const undo = document.querySelector("#undo"); - const redo = document.querySelector("#redo"); - function element(tag, className, text) { + const form = root.querySelector("form")!; + const dialog = root.querySelector("dialog")!; + const undo = document.querySelector("#undo")!; + const redo = document.querySelector("#redo")!; + function element(tag: K, className: string, text?: string) { const node = document.createElement(tag); node.className = className; if (text !== undefined) node.textContent = text; return node; } - function field(id, label, parent, required = false) { + function field(id: string|undefined, label: string|undefined, parent: HTMLElement, required = false) { const node = element("div", "brief-field"); node.dataset.field = id; const heading = element("div", "field-heading"); @@ -64,14 +65,14 @@ export function createMultiplayerForm(room) { function publish() { room.awareness.setLocalStateField("multiplayerForm", activeField ? { field: activeField, action: readOnly ? "viewing" : "editing" } : null); } - function activate(id) { + function activate(id: any) { if (activeField === id) return; model.history.stopCapturing(); activeField = id; publish(); } for (const spec of textFields) { - const node = field(spec.id, spec.label, root.querySelector("#text-fields"), true); + const node = field(spec.id, spec.label, root.querySelector("#text-fields")!, true); const mount = element("div", `brief-text ${spec.id === "name" ? "brief-name" : ""}`); node.insertBefore(mount, fields.get(spec.id).error); const permission = new Compartment(); @@ -80,7 +81,7 @@ export function createMultiplayerForm(room) { doc: model.texts[spec.id].toString(), extensions: [ permission.of(EditorState.readOnly.of(true)), - keymap.of([...yUndoManagerKeymap.map(binding => ({ ...binding, run: view => readOnly || binding.run(view) })), ...defaultKeymap]), + keymap.of([...yUndoManagerKeymap.map(binding => ({ ...binding, run: (view: EditorView) => readOnly || (binding.run?.(view) ?? false) })), ...defaultKeymap]), drawSelection(), EditorView.lineWrapping, placeholder(spec.placeholder), EditorView.contentAttributes.of({ id: `field-${spec.id}`, "aria-labelledby": `label-${spec.id}`, "aria-describedby": `people-${spec.id} error-${spec.id}`, "aria-required": "true", spellcheck: "true" }), @@ -95,8 +96,8 @@ export function createMultiplayerForm(room) { on(fields.get(spec.id).title, "click", () => editor.focus()); editors.set(spec.id, { editor, permission }); } - function input(id, label, tag, values, required = false) { - const node = field(id, label, root.querySelector("#detail-fields"), required); + function input(id: string, label: string, tag: "select" | "input", values: string[] = [], required = false) { + const node = field(id, label, root.querySelector("#detail-fields")!, required); const control = document.createElement(tag); control.id = `field-${id}`; control.name = id; @@ -109,7 +110,7 @@ export function createMultiplayerForm(room) { option.textContent = value || "Choose a team"; control.append(option); } - } else control.type = "date"; + } else (control as HTMLInputElement).type = "date"; node.insertBefore(control, fields.get(id).error); fields.get(id).control = control; on(control, "change", () => { if (!readOnly) model.set(id, control.value); }); @@ -117,7 +118,7 @@ export function createMultiplayerForm(room) { input("team", "Team", "select", ["", ...teams], true); input("priority", "Priority", "select", priorities); input("date", "Target date", "input"); - const channelNode = field("channels", "Launch channels", root.querySelector("#channel-field")); + const channelNode = field("channels", "Launch channels", root.querySelector("#channel-field")!); const choices = element("div", "channel-options"); choices.setAttribute("role", "group"); choices.setAttribute("aria-labelledby", "label-channels"); @@ -135,14 +136,14 @@ export function createMultiplayerForm(room) { channelNode.insertBefore(choices, fields.get("channels").error); channelNode.append(element("p", "note", "Choose as many as you need. You can decide later.")); - on(form, "focusin", event => { - const id = event.target.closest("[data-field]")?.dataset.field; + on(form, "focusin", (event) => { + const id = (event.target as Element).closest("[data-field]")?.dataset.field; if (id) activate(id); }); - on(form, "focusout", event => { - const id = event.target.closest("[data-field]")?.dataset.field; + on(form, "focusout", (event) => { + const id = (event.target as Element).closest("[data-field]")?.dataset.field; if (id) { touched.add(id); render(); } - if (event.relatedTarget?.closest("[data-field]")?.dataset.field !== activeField) { + if ((event.relatedTarget as Element | null)?.closest("[data-field]")?.dataset.field !== activeField) { activeField = null; model.history.stopCapturing(); publish(); @@ -155,42 +156,42 @@ export function createMultiplayerForm(room) { } on(window, "blur", clearPresence); on(window, "focus", () => { - const id = document.activeElement?.closest("[data-field]")?.dataset.field; + const id = document.activeElement?.closest("[data-field]")?.dataset.field; if (id) activate(id); }); on(document, "visibilitychange", () => { if (document.hidden) clearPresence(); }); on(undo, "click", () => { if (!readOnly) model.history.undo(); }); on(redo, "click", () => { if (!readOnly) model.history.redo(); }); - on(form, "keydown", event => { + on(form, "keydown", (event) => { // Text editors handle these themselves, including cursor restoration. - if (event.target.closest(".cm-editor") || event.isComposing || !(event.metaKey || event.ctrlKey) || event.altKey) return; + if ((event.target as Element).closest(".cm-editor") || event.isComposing || !(event.metaKey || event.ctrlKey) || event.altKey) return; const key = event.key.toLowerCase(); if (!["z", "y"].includes(key)) return; event.preventDefault(); if (!readOnly) key === "y" || event.shiftKey ? model.history.redo() : model.history.undo(); }); - on(form, "submit", event => { + on(form, "submit", (event) => { event.preventDefault(); for (const id of fields.keys()) touched.add(id); render(); const errors = validate(model.values()); const first = Object.keys(errors)[0]; if (first) { - root.querySelector("#brief-announcement").textContent = "Complete the highlighted fields to review your brief."; + root.querySelector("#brief-announcement")!.textContent = "Complete the highlighted fields to review your brief."; if (editors.has(first)) editors.get(first).editor.focus(); else fields.get(first).control.focus(); return; } - root.querySelector("#brief-announcement").textContent = ""; + root.querySelector("#brief-announcement")!.textContent = ""; renderPreview(model.values()); dialog.showModal(); }); - on(root.querySelector("#close-preview"), "click", () => dialog.close()); + on(root.querySelector("#close-preview")!, "click", () => dialog.close()); - function renderPreview(values) { + function renderPreview(values: import("./model.ts").FormValues) { const entries = [...textFields.map(spec => [spec.label, values[spec.id]]), ["Team", values.team], ["Priority", values.priority], ["Target date", values.date], ["Launch channels", values.channels.join(", ")]]; - root.querySelector("#preview-content").replaceChildren(...entries.flatMap(([label, value]) => [ + root.querySelector("#preview-content")!.replaceChildren(...entries.flatMap(([label, value]) => [ element("dt", "", label), element("dd", "", value || "Not set"), ])); } @@ -204,7 +205,7 @@ export function createMultiplayerForm(room) { const errors = validate(values); for (const [id, { control, error }] of fields) { if (control) { - if (control.value !== values[id]) control.value = values[id]; + if (control.value !== String(values[id as keyof typeof values] ?? "")) control.value = String(values[id as keyof typeof values] ?? ""); control.disabled = readOnly; } const message = touched.has(id) ? errors[id] ?? "" : ""; @@ -213,13 +214,13 @@ export function createMultiplayerForm(room) { const target = control ?? editors.get(id)?.editor.contentDOM; if (target) target.setAttribute("aria-invalid", String(Boolean(message))); } - for (const checkbox of choices.querySelectorAll("input")) { + for (const checkbox of choices.querySelectorAll("input")) { checkbox.checked = values.channels.includes(checkbox.value); checkbox.disabled = readOnly; } const complete = requiredFields.filter(id => !errors[id]).length; - root.querySelector("#brief-progress").value = complete; - root.querySelector("#brief-completion").textContent = `${complete} of ${requiredFields.length} complete`; + root.querySelector("#brief-progress")!.value = complete; + root.querySelector("#brief-completion")!.textContent = `${complete} of ${requiredFields.length} complete`; if (dialog.open) renderPreview(values); renderHistory(); } @@ -239,7 +240,7 @@ export function createMultiplayerForm(room) { for (const text of Object.values(model.texts)) text.observe(render); model.properties.observe(render); room.awareness.on("change", renderPresence); - for (const event of ["stack-item-added", "stack-item-popped", "stack-cleared", "stack-item-updated"]) model.history.on(event, renderHistory); + for (const event of ["stack-item-added", "stack-item-popped", "stack-cleared", "stack-item-updated"] as const) model.history.on(event, renderHistory); render(); function destroy() { disposed = true; @@ -253,7 +254,7 @@ export function createMultiplayerForm(room) { dialog.close(); root.replaceChildren(); } - destroy.setReadOnly = value => { + destroy.setReadOnly = (value: boolean) => { if (value !== readOnly) { readOnly = value; for (const { editor, permission } of editors.values()) editor.dispatch({ effects: permission.reconfigure(EditorState.readOnly.of(value)) }); diff --git a/examples/collaboration/multiplayer-form/model.test.js b/examples/collaboration/multiplayer-form/model.test.ts similarity index 97% rename from examples/collaboration/multiplayer-form/model.test.js rename to examples/collaboration/multiplayer-form/model.test.ts index 1d64ee7..26ab582 100644 --- a/examples/collaboration/multiplayer-form/model.test.js +++ b/examples/collaboration/multiplayer-form/model.test.ts @@ -1,7 +1,7 @@ import { test } from "node:test"; import assert from "node:assert/strict"; import * as Y from "yjs"; -import { createFormModel, validate } from "./model.js"; +import { createFormModel, validate } from "./model.ts"; test("form fields and separate channel choices merge, while conflicting choices converge", () => { const a = new Y.Doc(); diff --git a/examples/collaboration/multiplayer-form/model.js b/examples/collaboration/multiplayer-form/model.ts similarity index 67% rename from examples/collaboration/multiplayer-form/model.js rename to examples/collaboration/multiplayer-form/model.ts index cbc937e..3fe3089 100644 --- a/examples/collaboration/multiplayer-form/model.js +++ b/examples/collaboration/multiplayer-form/model.ts @@ -1,24 +1,25 @@ import * as Y from "yjs"; +export interface FormValues { name: string; goal: string; audience: string; team: string; date: string; priority: string; channels: string[] } export const textFields = [ { id: "name", label: "Project name", placeholder: "Give your project a name", max: 120 }, { id: "goal", label: "What are we making?", placeholder: "Describe the idea and the problem it solves…", max: 2000 }, { id: "audience", label: "Who is it for?", placeholder: "Who will use it, and what do they need?", max: 1000 }, -]; +] as const; export const teams = ["Product", "Design", "Engineering", "Marketing", "Operations"]; export const priorities = ["Normal", "High", "Urgent"]; export const channels = ["Website", "Email", "Social", "In-app"]; export const requiredFields = [...textFields.map(field => field.id), "team"]; -function validDate(value) { +function validDate(value: string) { if (value === "") return true; if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) return false; const date = new Date(`${value}T00:00:00Z`); return Number.isFinite(date.getTime()) && date.toISOString().slice(0, 10) === value; } -export function validate(values) { - const errors = {}; +export function validate(values: Omit & Partial>) { + const errors: Record = {}; for (const field of textFields) { if (!values[field.id].trim()) errors[field.id] = `${field.label} is required.`; else if (values[field.id].length > field.max) errors[field.id] = `Use ${field.max} characters or fewer.`; @@ -28,7 +29,7 @@ export function validate(values) { return errors; } -export function createFormModel(doc) { +export function createFormModel(doc: Y.Doc) { // Top-level names avoid competing initializations of nested shared text. const texts = Object.fromEntries(textFields.map(field => [field.id, doc.getText(`multiplayer-form:${field.id}:v1`)])); const properties = doc.getMap("multiplayer-form:properties:v1"); @@ -36,17 +37,17 @@ export function createFormModel(doc) { const history = new Y.UndoManager([...Object.values(texts), properties], { trackedOrigins: new Set([origin]) }); return { texts, properties, history, - values() { - return { ...Object.fromEntries(Object.entries(texts).map(([id, text]) => [id, text.toString()])), - team: teams.includes(properties.get("team")) ? properties.get("team") : "", - priority: priorities.includes(properties.get("priority")) ? properties.get("priority") : "Normal", - date: typeof properties.get("date") === "string" ? properties.get("date") : "", + values(): FormValues { + return { name: texts.name.toString(), goal: texts.goal.toString(), audience: texts.audience.toString(), + team: teams.includes(String(properties.get("team"))) ? String(properties.get("team")) : "", + priority: priorities.includes(String(properties.get("priority"))) ? String(properties.get("priority")) : "Normal", + date: typeof properties.get("date") === "string" ? String(properties.get("date")) : "", channels: channels.filter(channel => properties.get(`channel:${channel}`) === true), }; }, - set(field, value) { - const valid = field === "team" ? teams.includes(value) || value === "" - : field === "priority" ? priorities.includes(value) + set(field: string, value: unknown) { + const valid = field === "team" ? (typeof value === "string" && teams.includes(value)) || value === "" + : field === "priority" ? (typeof value === "string" && priorities.includes(value)) : field === "date" ? typeof value === "string" && validDate(value) : field.startsWith("channel:") && channels.includes(field.slice(8)) && typeof value === "boolean"; if (!valid || properties.get(field) === value) return; diff --git a/examples/collaboration/next.config.ts b/examples/collaboration/next.config.ts new file mode 100644 index 0000000..6ff2eaa --- /dev/null +++ b/examples/collaboration/next.config.ts @@ -0,0 +1,8 @@ +import type { NextConfig } from "next"; +const config: NextConfig = { + output: "export", + images: { unoptimized: true }, + poweredByHeader: false, + transpilePackages: ["@synixir/client"], +}; +export default config; diff --git a/examples/collaboration/package.json b/examples/collaboration/package.json index 3071c69..56e1b84 100644 --- a/examples/collaboration/package.json +++ b/examples/collaboration/package.json @@ -6,25 +6,39 @@ "node": ">=22.12.0" }, "scripts": { - "dev": "vite", - "build": "vite build", - "test": "playwright test" + "dev": "node --import tsx dev.ts", + "build": "next build", + "typecheck": "next typegen && tsc --noEmit", + "test": "playwright test", + "predev": "npm run build --workspace @synixir/client" }, "dependencies": { "@codemirror/commands": "6.11.0", "@codemirror/state": "6.7.4", "@codemirror/view": "6.43.11", "@synixir/client": "0.1.0", + "@tailwindcss/postcss": "4.3.3", "@tiptap/core": "3.31.3", "@tiptap/extension-collaboration": "3.31.3", "@tiptap/pm": "3.31.3", "@tiptap/starter-kit": "3.31.3", "@tiptap/y-tiptap": "3.0.9", + "class-variance-authority": "0.7.1", + "cn": "0.3.0", + "lucide-react": "1.45.0", + "next": "16.3.5", + "radix-ui": "1.6.7", + "react": "19.3.0", + "react-dom": "19.3.0", + "shadcn": "4.21.0", + "tailwindcss": "4.3.3", + "tw-animate-css": "1.4.0", "y-codemirror.next": "0.3.6", "yjs": "13.6.32" }, "devDependencies": { "@playwright/test": "1.63.0", - "vite": "8.2.2" + "@types/react": "19.2.18", + "@types/react-dom": "19.2.7" } } diff --git a/examples/collaboration/playwright.config.js b/examples/collaboration/playwright.config.ts similarity index 82% rename from examples/collaboration/playwright.config.js rename to examples/collaboration/playwright.config.ts index 1623faf..76b3a6c 100644 --- a/examples/collaboration/playwright.config.js +++ b/examples/collaboration/playwright.config.ts @@ -12,7 +12,7 @@ export default defineConfig({ webServer: [ { command: "npm run dev -- --port 5174", - env: { SYNIXIR_ENDPOINT: "http://127.0.0.1:4010" }, + env: { NEXT_PUBLIC_SYNIXIR_BROWSER_TEST: "true", SYNIXIR_ENDPOINT: "http://127.0.0.1:4010" }, url: "http://127.0.0.1:5174", reuseExistingServer: false, gracefulShutdown: { signal: "SIGTERM", timeout: 5_000 }, diff --git a/examples/collaboration/postcss.config.ts b/examples/collaboration/postcss.config.ts new file mode 100644 index 0000000..a9b5c43 --- /dev/null +++ b/examples/collaboration/postcss.config.ts @@ -0,0 +1 @@ +export default { plugins: { "@tailwindcss/postcss": {} } }; diff --git a/examples/collaboration/presence.js b/examples/collaboration/presence.js deleted file mode 100644 index 56aa475..0000000 --- a/examples/collaboration/presence.js +++ /dev/null @@ -1,55 +0,0 @@ -const colors = ["#3565b0", "#9a4626", "#7a4daa", "#26735b", "#aa3864", "#74601b"]; - -export function showParticipants(room, username, { avatars = false } = {}) { - const awareness = room.awareness; - const list = document.querySelector("#participants"); - const count = document.querySelector("#participant-count"); - const color = colors[awareness.clientID % colors.length]; - const name = username; - awareness.setLocalStateField("user", { name, color, colorLight: `${color}26` }); - document.querySelector("#your-name").textContent = name; - - let previousRoster; - function render() { - const online = room.state.connection === "connected"; - const participants = [...awareness.getStates()] - .filter(([id, state]) => state.user && (online || id === awareness.clientID)) - .sort(([a], [b]) => a === awareness.clientID ? -1 : b === awareness.clientID ? 1 : a - b); - // Awareness also changes for every cursor packet. Rebuild the roster only - // when the people or their displayed details change. - const roster = JSON.stringify([online, participants.map(([id, state]) => [id, state.user.name, state.user.color])]); - if (roster === previousRoster) return; - previousRoster = roster; - list.replaceChildren(...participants.map(([id, state]) => { - const local = id === awareness.clientID; - const item = document.createElement("li"); - const dot = document.createElement("span"); - dot.className = "participant-dot"; - dot.setAttribute("aria-hidden", "true"); - dot.style.backgroundColor = /^#[0-9a-f]{6}$/i.test(state.user.color) ? state.user.color : colors[0]; - const label = document.createElement("span"); - label.className = "participant-name"; - label.textContent = typeof state.user.name === "string" ? state.user.name.slice(0, 32) : "Guest"; - if (avatars) { - dot.classList.add("participant-avatar"); - dot.textContent = label.textContent.slice(0, 2).toUpperCase(); - } - const detail = document.createElement("span"); - detail.className = "participant-detail"; - detail.textContent = local ? online ? "You" : "You · offline" : "Online"; - item.append(dot, label, detail); - return item; - })); - const countLabel = online ? `${participants.length} online` : "Offline"; - // Cursor movement also triggers change; only announce actual count changes. - if (count.textContent !== countLabel) count.textContent = countLabel; - } - - awareness.on("change", render); - const unsubscribe = room.subscribe(render); - - return () => { - awareness.off("change", render); - unsubscribe(); - }; -} diff --git a/examples/collaboration/rich-text.html b/examples/collaboration/rich-text.html deleted file mode 100644 index c68c676..0000000 --- a/examples/collaboration/rich-text.html +++ /dev/null @@ -1,99 +0,0 @@ - - - - - - Synixir · Rich text - - - -
    -
    - Synixir - -
    - - - - - - -
    - - - diff --git a/examples/collaboration/rich-text.js b/examples/collaboration/rich-text.js deleted file mode 100644 index 7d39e22..0000000 --- a/examples/collaboration/rich-text.js +++ /dev/null @@ -1,5 +0,0 @@ -import { startExample } from "./example-shell.js"; -import { createRichText } from "./rich-text/editor.js"; -import "./rich-text/editor.css"; - -startExample({ createSurface: createRichText, pagePath: "/rich-text.html", presenceOptions: { avatars: true } }); diff --git a/examples/collaboration/rich-text/editor.js b/examples/collaboration/rich-text/editor.ts similarity index 64% rename from examples/collaboration/rich-text/editor.js rename to examples/collaboration/rich-text/editor.ts index cd6f95c..a6b4c65 100644 --- a/examples/collaboration/rich-text/editor.js +++ b/examples/collaboration/rich-text/editor.ts @@ -1,38 +1,40 @@ -import { Editor, Extension } from "@tiptap/core"; +import type { SynixirRoom } from "@synixir/client"; +import { CommandProps, Editor, Extension } from "@tiptap/core"; import StarterKit from "@tiptap/starter-kit"; import Collaboration, { isChangeOrigin } from "@tiptap/extension-collaboration"; import { Plugin } from "@tiptap/pm/state"; import { yCursorPlugin } from "@tiptap/y-tiptap"; +import { Awareness } from "y-protocols/awareness.js"; -const color = user => /^#[0-9a-f]{6}$/i.test(user?.color) ? user.color : "#3565b0"; -const safeUrl = value => { +const color = (user: { color: string; }) => /^#[0-9a-f]{6}$/i.test(user?.color) ? user.color : "#3565b0"; +const safeUrl = (value: string|URL) => { try { return ["https:", "http:"].includes(new URL(value).protocol); } catch { return false; } }; // CodeMirror also publishes `cursor`. Keep these relative positions separate, // while exposing the field the ProseMirror binding expects to read and write. -function documentAwareness(awareness) { - const state = value => value && ({ ...value, cursor: value.richTextCursor }); +function documentAwareness(awareness: Awareness) { + const state = (value: { [x: string]: any; }|null) => value && ({ ...value, cursor: value.richTextCursor }); return { getStates: () => new Map([...awareness.getStates()].map(([id, value]) => [id, state(value)])), getLocalState: () => state(awareness.getLocalState()), - setLocalStateField: (key, value) => awareness.setLocalStateField(key === "cursor" ? "richTextCursor" : key, value), - on: (...args) => awareness.on(...args), - off: (...args) => awareness.off(...args), + setLocalStateField: (key: string, value: any) => awareness.setLocalStateField(key === "cursor" ? "richTextCursor" : key, value), + on: awareness.on.bind(awareness), + off: awareness.off.bind(awareness), }; } const SharedHistory = Collaboration.extend({ addCommands() { // Yjs undo changes the shared document directly, even in a read-only view. - return Object.fromEntries(Object.entries(this.parent()).map(([name, command]) => [name, - (...args) => props => this.editor.isEditable && command(...args)(props), - ])); + const parent = this.parent?.(); + return { undo: () => props => this.editor.isEditable && !!parent?.undo?.()(props), + redo: () => props => this.editor.isEditable && !!parent?.redo?.()(props) }; }, }); -export function createRichText(room) { - const root = document.querySelector("#editor"); +export function createRichText(room: SynixirRoom) { + const root = document.querySelector("#editor")!; root.innerHTML = `
    - -

    Starting

    -

    Offline changes stay in this tab. Wait for saved before leaving.

    - - - - diff --git a/examples/collaboration/sdk.js b/examples/collaboration/sdk.js deleted file mode 100644 index 3f358bf..0000000 --- a/examples/collaboration/sdk.js +++ /dev/null @@ -1,53 +0,0 @@ -import { SynixirRoom } from "@synixir/client"; - -// A second consumer: no editor binding or knowledge of Phoenix channel events. -export let room; -const input = document.querySelector("#sdk-title"); -const button = document.querySelector("#sdk-connection"); -const status = document.querySelector("#sdk-state"); -const roomId = new URL(location.href).searchParams.get("room"); -let dispose = () => {}; - -try { - const { user } = await (await fetch("/api/session", { signal: AbortSignal.timeout(10_000) })).json(); - if (!user || !roomId) throw new Error("Sign in and provide a room in the URL."); - room = new SynixirRoom({ roomId, userId: user.id }); - const settings = room.doc.getMap("settings"); - room.awareness.setLocalStateField("page", "settings"); - const renderTitle = () => { input.value = settings.get("title") ?? ""; }; - settings.observe(renderTitle); - const unsubscribe = room.subscribe(state => { - input.disabled = state.readOnly; - status.textContent = `${state.connection} · ${state.saveStatus}${state.error ? ` · ${state.error.code}` : ""}`; - button.disabled = state.connection === "destroyed"; - button.textContent = ["connecting", "connected", "reconnecting"].includes(state.connection) ? "Disconnect" : "Connect"; - if (state.error?.code === "account_changed") { - dispose(); - input.value = ""; - } - }); - input.addEventListener("input", () => { if (!room.state.readOnly) settings.set("title", input.value); }); - button.addEventListener("click", () => { - if (button.textContent === "Disconnect") void room.disconnect(); - else void room.connect().catch(() => {}); - }); - dispose = () => { - unsubscribe(); - settings.unobserve(renderTitle); - input.disabled = true; - button.disabled = true; - void room.destroy(); - }; - await room.connect().catch(() => {}); -} catch (error) { - status.textContent = error.message; -} - -window.addEventListener("pagehide", () => dispose()); -window.addEventListener("storage", event => { - if (event.key === "synixir:session-change") { - dispose(); - input.value = ""; - location.reload(); - } -}); diff --git a/examples/collaboration/style.css b/examples/collaboration/style.css deleted file mode 100644 index 195a14c..0000000 --- a/examples/collaboration/style.css +++ /dev/null @@ -1,115 +0,0 @@ -:root { - font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; - color: #243a34; - background: #f4f5f0; - font-synthesis: none; - text-rendering: optimizeLegibility; -} - -* { box-sizing: border-box; } -body { margin: 0; } -main { max-width: 1200px; margin: 0 auto; padding: 0 36px; } -button, input { font: inherit; } -button { cursor: pointer; } -button:disabled { cursor: default; opacity: 0.4; } -button, input { border: 1px solid #b6c2b9; border-radius: 6px; padding: 9px 13px; } -button { background: #245e48; color: #fff; font-size: 13px; font-weight: 600; } -button:hover:not(:disabled) { background: #194832; } -input { width: 100%; background: #fff; color: inherit; min-width: 0; } -:focus-visible { outline: 3px solid #357c63; outline-offset: 3px; } -.masthead { display: flex; align-items: center; justify-content: space-between; padding: 26px 0; border-bottom: 1px solid #d7ddd4; } -.brand { display: flex; align-items: center; gap: 10px; font-size: 19px; font-weight: 700; letter-spacing: -0.6px; color: inherit; text-decoration: none; } -.brand > span { display: grid; place-items: center; width: 32px; height: 32px; border-radius: 7px; background: #245e48; color: #fff; font-size: 20px; } -.demo-label, .note, .intro, .page-footer { color: #5b6d63; } -.demo-label { font-size: 12px; } -.example-links { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 10px 20px; margin-left: 20px; } -.workspace-heading { display: flex; align-items: flex-end; justify-content: space-between; gap: 24px; margin: 40px 0 28px; } -.eyebrow { margin: 0 0 10px; font-size: 11px; font-weight: 650; letter-spacing: 1.4px; text-transform: uppercase; color: #416b53; } -h1 { font-size: clamp(26px, 4vw, 36px); font-weight: 600; line-height: 1.15; letter-spacing: -1.2px; margin: 0; } -.intro { font-size: 14px; margin: 12px 0 0; line-height: 1.6; } -.connection-controls { display: flex; align-items: center; gap: 16px; flex-shrink: 0; padding-bottom: 3px; } -#status { font-size: 12px; display: flex; align-items: center; gap: 7px; } -#status::before { content: ""; display: block; width: 7px; height: 7px; border-radius: 50%; background: #ac771e; } -#status[data-state="connected"]::before { background: #397b50; } -#status[data-state="disconnected"]::before, #status[data-state="error"]::before { background: #96634f; } -button.subtle, .history-controls button { background: transparent; color: #365344; } -button.subtle:hover, .history-controls button:hover:not(:disabled) { background: #e9eee5; } -.workspace { display: grid; grid-template-columns: minmax(0, 1fr) 258px; gap: 26px; align-items: start; } -.document-panel { min-width: 0; background: #fff; border: 1px solid #cbd5ca; border-radius: 9px; box-shadow: 0 4px 20px #243a3405; overflow: hidden; } -.editor-toolbar { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 12px 18px; border-bottom: 1px solid #e0e6dd; background: #fcfdf9; } -.document-title { font-size: 13px; font-weight: 600; overflow-wrap: anywhere; min-width: 0; } -.history-controls { display: flex; gap: 5px; flex-shrink: 0; } -.history-controls button { border-color: transparent; padding: 6px 8px; font-size: 12px; } -#editor .cm-editor { min-height: 470px; font-size: 16px; } -#editor .cm-editor.cm-focused { outline: 2px solid #357c63; outline-offset: -2px; } -#editor .cm-scroller { font-family: inherit; line-height: 1.85; overflow: auto; } -#editor .cm-content { padding: 34px 28px 80px; min-height: 470px; caret-color: #245e48; } -#editor .cm-line { position: relative; padding: 0; } -/* Placeholder text is a hint at document position zero, not layout space - before a collaborator's caret at that same position. */ -#editor .cm-placeholder { position: absolute; inset-inline: 0; top: 0; color: #78847d; } -#editor .cm-ySelectionInfo { font-family: inherit; border-radius: 3px 3px 3px 0; padding: 2px 5px; } -.editor-footer { display: flex; justify-content: space-between; gap: 15px; padding: 13px 20px; font-size: 11px; color: #5b6d63; background: #fcfdf9; border-top: 1px solid #e0e6dd; } -#save-status[data-state="saved"] { color: #286140; } -#save-status[data-state="unsaved"] { color: #885515; } -#save-status[data-state="failed"] { color: #a33b2b; } -aside { min-width: 0; } -aside section { margin-bottom: 24px; } -h2 { font-size: 13px; font-weight: 650; margin: 0 0 16px; } -label { display: block; font-size: 12px; margin-bottom: 7px; } -#room-form input { font-size: 13px; } -#room-form button { width: 100%; margin-top: 9px; } -.note { font-size: 12px; line-height: 1.65; margin: 12px 0 0; } -.people-panel { border-top: 1px solid #d7ddd4; padding-top: 23px; } -.section-heading { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; } -#participant-count { font-size: 11px; color: #5b6d63; white-space: nowrap; } -#participants { list-style: none; margin: 0; padding: 0; } -#participants li { display: flex; align-items: center; gap: 9px; padding: 10px 0; font-size: 12px; } -.participant-dot { height: 9px; width: 9px; flex-shrink: 0; border-radius: 50%; } -.participant-name { overflow-wrap: anywhere; min-width: 0; } -.participant-detail { color: #65766b; font-size: 10px; white-space: nowrap; margin-left: auto; } -.page-footer { display: flex; justify-content: space-between; gap: 24px; border-top: 1px solid #d7ddd4; margin: 30px 0; padding-top: 5px; font-size: 11px; line-height: 1.7; } -.page-footer p { max-width: 50%; } - -@media (max-width: 760px) { - main { padding: 0 20px; } - .workspace-heading { display: block; margin-top: 28px; } - .connection-controls { margin-top: 18px; justify-content: space-between; } - .workspace { grid-template-columns: minmax(0, 1fr); gap: 25px; } - aside { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 20px; } - .people-panel { border-top: 0; padding-top: 0; } - aside > .note { grid-column: 1 / -1; margin-top: -20px; } - #editor .cm-editor, #editor .cm-content { min-height: 350px; } - #editor .cm-content { padding: 30px 20px 60px; } - .section-heading { display: block; } - .section-heading h2 { margin-bottom: 5px; } - #participants li { flex-wrap: wrap; } - .participant-detail { margin-left: 18px; } - .page-footer { display: block; } - .page-footer p { max-width: none; } -} - -@media (prefers-reduced-motion: reduce) { - #editor .cm-ySelectionInfo, #editor .cm-ySelectionCaretDot { transition: none; } -} - -[hidden] { display: none !important; } -.account-panel { max-width: 520px; margin: 40px auto; } -.account-panel p { line-height: 1.6; } -.account-panel form { display: grid; gap: 12px; margin-top: 24px; } -.account-panel label { margin: 0; } -.account-bar, .actions { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin: 18px 0; } -.account-bar > span { min-width: 0; overflow-wrap: anywhere; } -.account-bar button { flex-shrink: 0; white-space: nowrap; } -#your-name { overflow-wrap: anywhere; } -#account-error, #member-error { color: #a33b2b; font-size: 13px; } -#room-list { padding-left: 20px; line-height: 2; } -#members-list { list-style: none; padding: 0; font-size: 12px; } -#members-list li { display: grid; gap: 7px; padding: 12px 0; border-bottom: 1px solid #d7ddd4; overflow-wrap: anywhere; } -#member-form { display: grid; gap: 8px; } -select { font: inherit; padding: 8px; border: 1px solid #b6c2b9; border-radius: 6px; background: white; } - -@media (max-width: 760px) { - #members-panel { grid-column: 1 / -1; } - .participant-name { flex: 1; } -} diff --git a/examples/collaboration/table.html b/examples/collaboration/table.html deleted file mode 100644 index e91ada9..0000000 --- a/examples/collaboration/table.html +++ /dev/null @@ -1,101 +0,0 @@ - - - - - - Synixir · Collaborative Table - - - - -
    -
    - Synixir - -
    - - - - - - -
    - - - diff --git a/examples/collaboration/table.js b/examples/collaboration/table.js deleted file mode 100644 index 1438642..0000000 --- a/examples/collaboration/table.js +++ /dev/null @@ -1,5 +0,0 @@ -import { startExample } from "./example-shell.js"; -import { createCollaborativeTable } from "./table/table.js"; -import "./table/table.css"; - -startExample({ createSurface: createCollaborativeTable, pagePath: "/table.html", presenceOptions: { avatars: true } }); diff --git a/examples/collaboration/table/model.test.js b/examples/collaboration/table/model.test.ts similarity index 98% rename from examples/collaboration/table/model.test.js rename to examples/collaboration/table/model.test.ts index b2ffa64..f9f5324 100644 --- a/examples/collaboration/table/model.test.js +++ b/examples/collaboration/table/model.test.ts @@ -1,7 +1,7 @@ import { test } from "node:test"; import assert from "node:assert/strict"; import * as Y from "yjs"; -import { createTableModel } from "./model.js"; +import { createTableModel } from "./model.ts"; function clients() { const a = new Y.Doc(), b = new Y.Doc(); diff --git a/examples/collaboration/table/model.js b/examples/collaboration/table/model.ts similarity index 72% rename from examples/collaboration/table/model.js rename to examples/collaboration/table/model.ts index baa01ae..25351b0 100644 --- a/examples/collaboration/table/model.js +++ b/examples/collaboration/table/model.ts @@ -1,28 +1,30 @@ import * as Y from "yjs"; export const limits = { rows: 100, columns: 12, pasteCharacters: 50000 }; +export interface AxisItem { id: string; order: number; label?: string } +type Axis = "rows" | "columns"; const initialRows = ["row-1", "row-2", "row-3"].map((id, order) => ({ id, order })); const initialColumns = ["Task", "Owner", "Status", "Due date"].map((label, order) => ({ id: `column-${order + 1}`, order, label })); -export const cellKey = (row, column) => JSON.stringify([row, column]); +export const cellKey = (row: string, column: string) => JSON.stringify([row, column]); -export function createTableModel(doc) { - const rows = doc.getMap("collaborative-table:rows:v1"); - const columns = doc.getMap("collaborative-table:columns:v1"); - const deleted = doc.getMap("collaborative-table:deleted:v1"); - const labels = doc.getMap("collaborative-table:labels:v1"); +export function createTableModel(doc: Y.Doc) { + const rows = doc.getMap("collaborative-table:rows:v1"); + const columns = doc.getMap("collaborative-table:columns:v1"); + const deleted = doc.getMap("collaborative-table:deleted:v1"); + const labels = doc.getMap("collaborative-table:labels:v1"); const origin = {}; const history = new Y.UndoManager([rows, columns, deleted, labels], { trackedOrigins: new Set([origin]) }); - const texts = new Map(); - function list(axis) { - const defaults = axis === "rows" ? initialRows : initialColumns; + const texts = new Map(); + function list(axis: Axis): AxisItem[] { + const defaults: AxisItem[] = axis === "rows" ? initialRows : initialColumns; const extra = axis === "rows" ? rows : columns; return [...defaults, ...[...extra].filter(([id, order]) => typeof id === "string" && Number.isFinite(order) && !defaults.some(item => item.id === id)) - .map(([id, order]) => ({ id, order }))] + .map(([id, order]): AxisItem => ({ id, order }))] .filter(item => !deleted.get(`${axis}:${item.id}`)) .sort((a, b) => a.order - b.order || a.id.localeCompare(b.id)) .map(item => axis === "rows" ? item : { ...item, label: String(labels.get(item.id) ?? item.label ?? "Column").slice(0, 80) }); } - function text(row, column) { + function text(row: string, column: string) { const key = cellKey(row, column); if (!texts.has(key)) { // Stable top-level names let two clients edit an untouched cell without @@ -31,16 +33,16 @@ export function createTableModel(doc) { texts.set(key, value); history.addToScope(value); } - return texts.get(key); + return texts.get(key)!; } - function action(fn) { + function action(fn: () => T): T { history.stopCapturing(); - let result; + let result!: T; doc.transact(() => { result = fn(); }, origin); history.stopCapturing(); return result; } - function append(axis) { + function append(axis: Axis) { const items = list(axis); if (items.length >= limits[axis]) return null; const id = crypto.randomUUID(); @@ -48,7 +50,7 @@ export function createTableModel(doc) { if (axis === "columns") labels.set(id, `Column ${items.length + 1}`); return id; } - function setCell(row, column, value) { + function setCell(row: string, column: string, value: string) { const cell = text(row, column); if (cell.toString() === value) return; cell.delete(0, cell.length); @@ -56,22 +58,22 @@ export function createTableModel(doc) { } return { history, list, text, - add(axis) { if (!["rows", "columns"].includes(axis)) return null; return action(() => append(axis)); }, - rename(id, value) { + add(axis: string) { if (axis !== "rows" && axis !== "columns") return null; return action(() => append(axis)); }, + rename(id: string, value: unknown) { if (typeof value !== "string" || !list("columns").some(item => item.id === id)) return; if (labels.get(id) !== value) doc.transact(() => labels.set(id, value.slice(0, 80)), origin); }, - remove(axis, id) { - if (!["rows", "columns"].includes(axis) || !list(axis).some(item => item.id === id)) return; + remove(axis: string, id: string) { + if ((axis !== "rows" && axis !== "columns") || !list(axis).some(item => item.id === id)) return; // Retain cell contents so undo can restore a deleted row or column, // including edits made concurrently by a disconnected collaborator. action(() => deleted.set(`${axis}:${id}`, true)); }, - clear(row, column) { + clear(row: string, column: string) { if (list("rows").some(item => item.id === row) && list("columns").some(item => item.id === column)) action(() => setCell(row, column, "")); }, - paste(row, column, value) { + paste(row: string, column: string, value: string) { if (typeof value !== "string" || value.length > limits.pasteCharacters) return false; const matrix = value.replace(/\r\n?/g, "\n").replace(/\n$/, "").split("\n").map(line => line.split("\t")); const rowIndex = list("rows").findIndex(item => item.id === row); diff --git a/examples/collaboration/table/table.js b/examples/collaboration/table/table.ts similarity index 69% rename from examples/collaboration/table/table.js rename to examples/collaboration/table/table.ts index 827bb5c..5bec126 100644 --- a/examples/collaboration/table/table.js +++ b/examples/collaboration/table/table.ts @@ -1,26 +1,27 @@ +import type { SynixirRoom } from "@synixir/client"; import { EditorView, drawSelection, keymap, placeholder } from "@codemirror/view"; import { Compartment, EditorState, Prec } from "@codemirror/state"; import { defaultKeymap } from "@codemirror/commands"; import { yCollab, yUndoManagerKeymap } from "y-codemirror.next"; -import { createTableModel, cellKey, limits } from "./model.js"; +import { createTableModel, cellKey, limits } from "./model.ts"; -export function createCollaborativeTable(room) { - const root = document.querySelector("#editor"); - document.querySelector("#open-peer").href = location.href; +export function createCollaborativeTable(room: SynixirRoom) { + const root = document.querySelector("#editor")!; + document.querySelector("#open-peer")!.href = location.href; const model = createTableModel(room.doc); const events = new AbortController(); - const on = (node, event, handler, capture = false) => node.addEventListener(event, handler, { signal: events.signal, capture }); - const make = (tag, className = "", text) => { + const on = (target: EventTarget, event: K, handler: (event: DocumentEventMap[K]) => void, capture = false) => target.addEventListener(event, handler as EventListener, { signal: events.signal, capture }); + const make = (tag: K, className = "", text?: string) => { const node = document.createElement(tag); node.className = className; if (text !== undefined) node.textContent = text; return node; }; - let readOnly = true, disposed = false, frame; - let selection = null, editing = null, presenceActive = false; - let rows = [], columns = []; + let readOnly = true, disposed = false, frame: number|undefined; + let selection: { [x: string]: any; row: any; column: any; }|null = null, editing: { view: any; mount: any; permission: any; }|null = null, presenceActive = false; + let rows: import("./model.ts").AxisItem[] = [], columns: import("./model.ts").AxisItem[] = []; const rowNodes = new Map(), headers = new Map(), cells = new Map(); - const undo = document.querySelector("#undo"), redo = document.querySelector("#redo"); - const announce = message => { root.querySelector("#table-announcement").textContent = message; }; + const undo = document.querySelector("#undo")!, redo = document.querySelector("#redo")!; + const announce = (message: string|null) => { root.querySelector("#table-announcement")!.textContent = message; }; root.innerHTML = `
    Select a cellEnter or double-click to edit · Paste from a spreadsheet
    @@ -29,11 +30,11 @@ export function createCollaborativeTable(room) {

    Changes save as you go. Select any cell to see who’s there.

    Select a cell to rename its column or remove a row.

    `; - const table = root.querySelector("table"), head = table.querySelector("thead tr"), body = table.querySelector("tbody"); - const nameInput = root.querySelector("#column-name"); - const addRow = root.querySelector("#add-row"), appendRow = root.querySelector("#append-row"), addColumn = root.querySelector("#add-column"); - const currentCell = () => selection && cells.get(cellKey(selection.row, selection.column)); - const address = (row, column) => `${String.fromCharCode(65 + columns.findIndex(item => item.id === column))}${rows.findIndex(item => item.id === row) + 1}`; + const table = root.querySelector("table")!, head = table.querySelector("thead tr")!, body = table.querySelector("tbody")!; + const nameInput = root.querySelector("#column-name")!; + const addRow = root.querySelector("#add-row")!, appendRow = root.querySelector("#append-row")!, addColumn = root.querySelector("#add-column")!; + const currentCell = () => selection && cells.get(cellKey(selection!.row, selection!.column)); + const address = (row: string, column: string) => `${String.fromCharCode(65 + columns.findIndex(item => item.id === column))}${rows.findIndex(item => item.id === row) + 1}`; function publish() { if (disposed) return; room.awareness.setLocalStateField("collaborativeTable", presenceActive && selection ? { ...selection, action: editing && !readOnly ? "editing" : "viewing" } : null); @@ -50,12 +51,12 @@ export function createCollaborativeTable(room) { if (cell) { cell.display.hidden = false; cell.node.classList.remove("is-editing"); - cell.display.textContent = model.text(selection.row, selection.column).toString(); + cell.display.textContent = model.text(selection!.row, selection!.column).toString(); if (focus) cell.node.focus(); } publish(); } - function choose(row, column, focus = true) { + function choose(row: any, column: any, focus = true) { if (selection?.row !== row || selection?.column !== column) { finishEditing(); selection = { row, column }; @@ -67,9 +68,9 @@ export function createCollaborativeTable(room) { if (focus) currentCell()?.node.focus({ preventScroll: true }); if (focus) currentCell()?.node.scrollIntoView({ block: "nearest", inline: "nearest" }); } - function navigate(dr, dc, wrap = false) { + function navigate(dr: number, dc: number, wrap = false) { if (!selection) return false; - let r = rows.findIndex(item => item.id === selection.row), c = columns.findIndex(item => item.id === selection.column); + let r = rows.findIndex(item => item.id === selection!.row), c = columns.findIndex(item => item.id === selection!.column); if (wrap) { const index = r * columns.length + c + dc; if (index < 0 || index >= rows.length * columns.length) { finishEditing(); return false; } @@ -81,10 +82,10 @@ export function createCollaborativeTable(room) { choose(rows[r].id, columns[c].id); return true; } - function beginEditing(initial) { + function beginEditing(initial?: string) { if (!selection || !currentCell()) return; if (editing) { editing.view.focus(); return; } - const cell = currentCell(), text = model.text(selection.row, selection.column); + const cell = currentCell(), text = model.text(selection!.row, selection!.column); const mount = make("div", "table-cell-editor"); cell.node.append(mount); cell.display.hidden = true; cell.node.classList.add("is-editing"); const permission = new Compartment(); @@ -97,9 +98,9 @@ export function createCollaborativeTable(room) { { key: "Tab", run: () => navigate(0, 1, true) }, { key: "Shift-Tab", run: () => navigate(0, -1, true) }, ])), - keymap.of([...yUndoManagerKeymap.map(binding => ({ ...binding, run: editor => readOnly || binding.run(editor) })), ...defaultKeymap]), + keymap.of([...yUndoManagerKeymap.map(binding => ({ ...binding, run: (editor: EditorView) => readOnly || (binding.run?.(editor) ?? false) })), ...defaultKeymap]), drawSelection(), EditorView.lineWrapping, placeholder("Enter a value"), - EditorView.contentAttributes.of({ "aria-label": `Edit ${address(selection.row, selection.column)}`, spellcheck: "true" }), + EditorView.contentAttributes.of({ "aria-label": `Edit ${address(selection!.row, selection!.column)}`, spellcheck: "true" }), Prec.highest(EditorView.domEventHandlers({ beforeinput: event => readOnly && ["historyUndo", "historyRedo"].includes(event.inputType) })), yCollab(text, room.awareness, { undoManager: model.history }), ] }); @@ -107,7 +108,7 @@ export function createCollaborativeTable(room) { presenceActive = true; publish(); view.focus(); if (initial !== undefined && !readOnly) view.dispatch({ changes: { from: 0, to: view.state.doc.length, insert: initial }, selection: { anchor: initial.length } }); } - function add(axis) { + function add(axis: "rows" | "columns") { if (readOnly) return; finishEditing(); const id = model.add(axis); @@ -120,56 +121,56 @@ export function createCollaborativeTable(room) { announce(axis === "rows" ? "Row added." : "Column added."); } on(addRow, "click", () => add("rows")); on(appendRow, "click", () => add("rows")); on(addColumn, "click", () => add("columns")); - on(table, "focusin", event => { - if (event.target.matches("[data-cell]")) choose(event.target.dataset.row, event.target.dataset.column, false); + on(table, "focusin", (event) => { + if ((event.target as Element).matches("[data-cell]")) choose((event.target as HTMLElement).dataset.row, (event.target as HTMLElement).dataset.column, false); }); - on(table, "click", event => { - if (event.target.closest(".cm-editor")) return; - const cell = event.target.closest("[data-cell]"); + on(table, "click", (event) => { + if ((event.target as Element).closest(".cm-editor")) return; + const cell = (event.target as Element).closest("[data-cell]"); if (cell) { finishEditing(); choose(cell.dataset.row, cell.dataset.column); } }); - on(table, "dblclick", event => { if (event.target.closest("[data-cell]")) beginEditing(); }); - on(table, "keydown", event => { - if (event.target.closest(".cm-editor") || !selection || event.isComposing) return; + on(table, "dblclick", (event) => { if ((event.target as Element).closest("[data-cell]")) beginEditing(); }); + on(table, "keydown", (event) => { + if ((event.target as Element).closest(".cm-editor") || !selection || event.isComposing) return; const delta = { ArrowLeft: [0, -1], ArrowRight: [0, 1], ArrowUp: [-1, 0], ArrowDown: [1, 0] }[event.key]; - if (delta) { event.preventDefault(); navigate(...delta); } + if (delta) { event.preventDefault(); navigate(delta[0], delta[1]); } else if (event.key === "Tab") { if (navigate(0, event.shiftKey ? -1 : 1, true)) event.preventDefault(); } else if (["Enter", "F2"].includes(event.key)) { event.preventDefault(); beginEditing(); } - else if (!readOnly && ["Backspace", "Delete"].includes(event.key)) { event.preventDefault(); model.clear(selection.row, selection.column); } + else if (!readOnly && ["Backspace", "Delete"].includes(event.key)) { event.preventDefault(); model.clear(selection!.row, selection!.column); } else if (!readOnly && event.key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey) { event.preventDefault(); beginEditing(event.key); } }); - on(table, "copy", event => { + on(table, "copy", (event) => { if (editing || !selection || !event.clipboardData) return; - event.preventDefault(); event.clipboardData.setData("text/plain", model.text(selection.row, selection.column).toString()); + event.preventDefault(); event.clipboardData.setData("text/plain", model.text(selection!.row, selection!.column).toString()); }); - on(table, "paste", event => { + on(table, "paste", (event) => { if (!selection || !event.clipboardData) return; const value = event.clipboardData.getData("text/plain"); if (editing && !/[\t\r\n]/.test(value)) return; event.preventDefault(); if (readOnly) return; finishEditing(true); - if (!model.paste(selection.row, selection.column, value)) announce(`Paste must fit within ${limits.rows} rows and ${limits.columns} columns, using at most ${limits.pasteCharacters} characters.`); + if (!model.paste(selection!.row, selection!.column, value)) announce(`Paste must fit within ${limits.rows} rows and ${limits.columns} columns, using at most ${limits.pasteCharacters} characters.`); else announce("Pasted cells. Undo reverses this paste."); }, true); on(nameInput, "focus", () => model.history.stopCapturing()); on(nameInput, "blur", () => model.history.stopCapturing()); - on(nameInput, "input", () => { if (!readOnly && selection) model.rename(selection.column, nameInput.value); }); - on(root.querySelector("#edit-cell"), "click", () => beginEditing()); - on(root.querySelector("#clear-cell"), "click", () => { if (!readOnly && selection) { finishEditing(); model.clear(selection.row, selection.column); } }); - for (const axis of ["row", "column"]) on(root.querySelector(`#delete-${axis}`), "click", () => { + on(nameInput, "input", () => { if (!readOnly && selection) model.rename(selection!.column, nameInput.value); }); + on(root.querySelector("#edit-cell")!, "click", () => beginEditing()); + on(root.querySelector("#clear-cell")!, "click", () => { if (!readOnly && selection) { finishEditing(); model.clear(selection!.row, selection!.column); } }); + for (const axis of ["row", "column"]) on(root.querySelector(`#delete-${axis}`)!, "click", () => { if (!readOnly && selection) { finishEditing(); model.remove(`${axis}s`, selection[axis]); announce(`${axis === "row" ? "Row" : "Column"} deleted. Undo restores its contents.`); } }); function history(redo = false) { if (!readOnly) { finishEditing(); redo ? model.history.redo() : model.history.undo(); } } on(undo, "click", () => history()); on(redo, "click", () => history(true)); - on(root, "keydown", event => { - if (event.target.closest(".cm-editor") || event.isComposing || !(event.ctrlKey || event.metaKey) || event.altKey) return; + on(root, "keydown", (event) => { + if ((event.target as Element).closest(".cm-editor") || event.isComposing || !(event.ctrlKey || event.metaKey) || event.altKey) return; if (["z", "y"].includes(event.key.toLowerCase())) { event.preventDefault(); history(event.shiftKey || event.key.toLowerCase() === "y"); } }); on(root, "focusin", () => { presenceActive = true; publish(); }); - on(root, "focusout", event => { - if (!root.contains(event.relatedTarget)) { presenceActive = false; room.awareness.setLocalStateField("cursor", null); publish(); } - if (editing && !editing.mount.contains(event.relatedTarget)) { + on(root, "focusout", (event) => { + if (!root.contains(event.relatedTarget as Node | null)) { presenceActive = false; room.awareness.setLocalStateField("cursor", null); publish(); } + if (editing && !editing.mount.contains(event.relatedTarget as Node | null)) { // Let pointer/keyboard handlers complete before removing the focused editor. queueMicrotask(() => { if (!disposed && editing && !editing.mount.contains(document.activeElement)) finishEditing(); }); } @@ -186,15 +187,15 @@ export function createCollaborativeTable(room) { cell.node.classList.toggle("is-selected", selected); cell.node.setAttribute("aria-selected", String(selected)); cell.node.tabIndex = selected || (!selection && cell.node.dataset.row === rows[0]?.id && cell.node.dataset.column === columns[0]?.id) ? 0 : -1; } - if (editing && selection) editing.view.contentDOM.setAttribute("aria-label", `Edit ${address(selection.row, selection.column)}`); + if (editing && selection) editing.view.contentDOM.setAttribute("aria-label", `Edit ${address(selection!.row, selection!.column)}`); const column = columns.find(item => item.id === selection?.column); - root.querySelector("#cell-address").textContent = selection ? address(selection.row, selection.column) : "Select a cell"; - root.querySelector("#table-selection-empty").hidden = Boolean(column); - root.querySelector("#table-selection-fields").hidden = !column; - if (column && nameInput.value !== column.label) nameInput.value = column.label; + root.querySelector("#cell-address")!.textContent = selection ? address(selection!.row, selection!.column) : "Select a cell"; + root.querySelector("#table-selection-empty")!.hidden = Boolean(column); + root.querySelector("#table-selection-fields")!.hidden = !column; + if (column && nameInput.value !== column.label) nameInput.value = column.label ?? ""; nameInput.readOnly = readOnly; - for (const id of ["clear-cell", "delete-row", "delete-column"]) root.querySelector(`#${id}`).disabled = readOnly; - root.querySelector("#edit-cell").textContent = readOnly ? "View cell" : "Edit cell"; + for (const id of ["clear-cell", "delete-row", "delete-column"]) root.querySelector(`#${id}`)!.disabled = readOnly; + root.querySelector("#edit-cell")!.textContent = readOnly ? "View cell" : "Edit cell"; } function renderPresence() { if (disposed) return; @@ -209,7 +210,7 @@ export function createCollaborativeTable(room) { } for (const [key, cell] of cells) { const current = byCell.get(key) ?? []; - const title = current.map(peer => `${peer.name} ${peer.action}`).join(" · "); + const title = current.map((peer: { name: any; action: any; }) => `${peer.name} ${peer.action}`).join(" · "); if (cell.people.textContent !== title) cell.people.textContent = title; cell.people.hidden = !current.length; cell.node.classList.toggle("has-collaborator", current.length > 0); @@ -218,13 +219,13 @@ export function createCollaborativeTable(room) { } // Move only out-of-order elements. Replacing a row would discard a teammate's // caret and interrupt the local editor on every document update. - function order(parent, children) { children.forEach((child, index) => { if (parent.children[index] !== child) parent.insertBefore(child, parent.children[index] ?? null); }); } + function order(parent: HTMLElement, children: any[]) { children.forEach((child: any, index: number) => { if (parent.children[index] !== child) parent.insertBefore(child, parent.children[index] ?? null); }); } function render() { if (disposed) return; rows = model.list("rows"); columns = model.list("columns"); - if (selection && (!rows.some(row => row.id === selection.row) || !columns.some(column => column.id === selection.column))) { + if (selection && (!rows.some(row => row.id === selection!.row) || !columns.some(column => column.id === selection!.column))) { finishEditing(); selection = null; publish(); - root.querySelector(".table-viewport").focus(); announce("The selected row or column was removed."); + root.querySelector(".table-viewport")!.focus(); announce("The selected row or column was removed."); } const rowIds = new Set(rows.map(row => row.id)), columnIds = new Set(columns.map(column => column.id)); for (const [key, cell] of cells) if (!rowIds.has(cell.node.dataset.row) || !columnIds.has(cell.node.dataset.column)) { cell.node.remove(); cells.delete(key); } @@ -258,8 +259,8 @@ export function createCollaborativeTable(room) { } order(body, rows.map(row => rowNodes.get(row.id))); table.setAttribute("aria-rowcount", String(rows.length + 1)); table.setAttribute("aria-colcount", String(columns.length + 1)); - root.querySelector("#table-count").textContent = `${rows.length} rows · ${columns.length} columns`; - root.querySelector("#table-empty").hidden = Boolean(rows.length && columns.length); + root.querySelector("#table-count")!.textContent = `${rows.length} rows · ${columns.length} columns`; + root.querySelector("#table-empty")!.hidden = Boolean(rows.length && columns.length); addRow.disabled = appendRow.disabled = readOnly || rows.length >= limits.rows; addColumn.disabled = readOnly || columns.length >= limits.columns; renderSelection(); renderPresence(); renderHistory(); @@ -267,15 +268,15 @@ export function createCollaborativeTable(room) { function scheduleRender() { if (!disposed && frame === undefined) frame = requestAnimationFrame(() => { frame = undefined; render(); }); } room.doc.on("afterTransaction", scheduleRender); room.awareness.on("change", renderPresence); - for (const event of ["stack-item-added", "stack-item-popped", "stack-cleared", "stack-item-updated"]) model.history.on(event, renderHistory); + for (const event of ["stack-item-added", "stack-item-popped", "stack-cleared", "stack-item-updated"] as const) model.history.on(event, renderHistory); render(); function destroy() { - disposed = true; cancelAnimationFrame(frame); events.abort(); + disposed = true; if (frame !== undefined) cancelAnimationFrame(frame); events.abort(); room.doc.off("afterTransaction", scheduleRender); room.awareness.off("change", renderPresence); finishEditing(); room.awareness.setLocalStateField("collaborativeTable", null); model.destroy(); cells.clear(); rowNodes.clear(); headers.clear(); root.replaceChildren(); } - destroy.setReadOnly = value => { + destroy.setReadOnly = (value: boolean) => { if (readOnly !== value) { readOnly = value; if (editing) editing.view.dispatch({ effects: editing.permission.reconfigure(EditorState.readOnly.of(value)) }); diff --git a/examples/collaboration/tests/access-helpers.js b/examples/collaboration/tests/access-helpers.ts similarity index 75% rename from examples/collaboration/tests/access-helpers.js rename to examples/collaboration/tests/access-helpers.ts index b28ceff..5193a2b 100644 --- a/examples/collaboration/tests/access-helpers.js +++ b/examples/collaboration/tests/access-helpers.ts @@ -1,10 +1,10 @@ -import { request, expect } from "@playwright/test"; +import { request, expect, Page, APIRequestContext } from "@playwright/test"; import { randomUUID } from "node:crypto"; export const password = "a browser test password of sufficient length"; const rooms = new Map(); -export async function api(context, baseURL, path, method = "GET", data) { +export async function api(context: APIRequestContext, baseURL: string|undefined, path: string, method = "GET", data?: { username?: string; password?: string; room_id?: string|null; role?: any; }) { const bootstrap = await context.get(`${baseURL}/api/session`); const session = await bootstrap.json(); const response = await context.fetch(`${baseURL}${path}`, { @@ -13,7 +13,7 @@ export async function api(context, baseURL, path, method = "GET", data) { return response; } -export async function register(context, baseURL, username = `user_${randomUUID().replaceAll("-", "").slice(0, 20)}`) { +export async function register(context: APIRequestContext, baseURL: string|undefined, username = `user_${randomUUID().replaceAll("-", "").slice(0, 20)}`) { const response = await api(context, baseURL, "/api/accounts", "POST", { username, password }); expect(response.ok(), await response.text()).toBe(true); return (await response.json()).user; @@ -21,7 +21,7 @@ export async function register(context, baseURL, username = `user_${randomUUID() // Legacy collaboration scenarios now run through actual accounts and membership // APIs. No test-only issuer or authentication bypass exists in the application. -export async function openRoom(page, url) { +export async function openRoom(page: Page, url: string|URL) { const baseURL = new URL(url).origin; const room = new URL(url).searchParams.get("room"); let session = await (await page.request.get(`${baseURL}/api/session`)).json(); @@ -41,5 +41,5 @@ export async function openRoom(page, url) { expect(granted.ok(), await granted.text()).toBe(true); record.users.add(user.id); } - return page.goto(url); + return page.goto(String(url)); } diff --git a/examples/collaboration/tests/browser-globals.d.ts b/examples/collaboration/tests/browser-globals.d.ts new file mode 100644 index 0000000..47f7788 --- /dev/null +++ b/examples/collaboration/tests/browser-globals.d.ts @@ -0,0 +1,7 @@ +export {}; +declare global { interface Window { + originalCellEditor: Element | null; + motionSample: Promise<{ frames: number[]; unrelatedMutations: number; samePointer: boolean; sameParticipant: boolean }>; + dragMotionSample: Promise<{ frames: { x: number; separation: number }[]; sameRing: boolean }>; + flowMotion: Promise<{ y: number; separation: number }[]>; +} } diff --git a/examples/collaboration/tests/collaboration.spec.js b/examples/collaboration/tests/collaboration.spec.ts similarity index 97% rename from examples/collaboration/tests/collaboration.spec.js rename to examples/collaboration/tests/collaboration.spec.ts index f638596..dccdd7d 100644 --- a/examples/collaboration/tests/collaboration.spec.js +++ b/examples/collaboration/tests/collaboration.spec.ts @@ -1,8 +1,8 @@ -import { openRoom } from "./access-helpers.js"; +import { openRoom } from "./access-helpers.ts"; import { randomUUID } from "node:crypto"; -import { test, expect } from "./fixtures.js"; +import { test, expect } from "./fixtures.ts"; -import { editor, documentText, expectText, insertAtStart, connected } from "./editor-helpers.js"; +import { editor, documentText, expectText, insertAtStart, connected } from "./editor-helpers.ts"; test("clients in different rooms keep their documents separate", async ({ browser, baseURL }) => { const contexts = await Promise.all([browser.newContext(), browser.newContext()]); @@ -80,8 +80,8 @@ test("awareness shows participants and selections and removes them on departure" const url = `${baseURL}/?room=presence-${randomUUID()}`; await Promise.all([openRoom(alice, url), openRoom(bob, url)]); await Promise.all([connected(alice), connected(bob)]); - const aliceName = await alice.locator("#your-name").textContent(); - const bobName = await bob.locator("#your-name").textContent(); + const aliceName = (await alice.locator("#your-name").textContent())!; + const bobName = (await bob.locator("#your-name").textContent())!; await expect(alice.getByRole("list", { name: "Participants" })).toContainText(bobName); await expect(bob.getByRole("list", { name: "Participants" })).toContainText(aliceName); await expect(bob.locator("#participant-count")).toHaveText("2 online"); diff --git a/examples/collaboration/tests/connection-pool.spec.js b/examples/collaboration/tests/connection-pool.spec.ts similarity index 86% rename from examples/collaboration/tests/connection-pool.spec.js rename to examples/collaboration/tests/connection-pool.spec.ts index d1197b9..d8eac7d 100644 --- a/examples/collaboration/tests/connection-pool.spec.js +++ b/examples/collaboration/tests/connection-pool.spec.ts @@ -1,8 +1,8 @@ import { request } from "@playwright/test"; import { randomUUID } from "node:crypto"; -import { test, expect } from "./fixtures.js"; -import { api, register } from "./access-helpers.js"; -import { connected } from "./editor-helpers.js"; +import { test, expect } from "./fixtures.ts"; +import { api, register } from "./access-helpers.ts"; +import { connected } from "./editor-helpers.ts"; test("open rooms do not exhaust database connections for new accounts", async ({ context, baseURL }) => { await register(context.request, baseURL); diff --git a/examples/collaboration/tests/editor-helpers.js b/examples/collaboration/tests/editor-helpers.js deleted file mode 100644 index 01abdba..0000000 --- a/examples/collaboration/tests/editor-helpers.js +++ /dev/null @@ -1,26 +0,0 @@ -import { expect } from "./fixtures.js"; - -export const editor = page => page.getByRole("textbox", { name: "Shared document" }); - -export async function documentText(page) { - return editor(page).locator(".cm-line").evaluateAll(lines => lines.map(line => { - const copy = line.cloneNode(true); - copy.querySelectorAll(".cm-ySelectionCaret, .cm-placeholder").forEach(node => node.remove()); - return copy.textContent; - }).join("\n")); -} - -export async function expectText(page, value) { - if (value instanceof RegExp) await expect.poll(() => documentText(page)).toMatch(value); - else await expect.poll(() => documentText(page)).toBe(value); -} - -export async function insertAtStart(page, value) { - await editor(page).click(); - await editor(page).press("ControlOrMeta+Home"); - await page.keyboard.insertText(value); -} - -export async function connected(page) { - await expect(page.locator("#status")).toHaveText("Connected"); -} diff --git a/examples/collaboration/tests/editor-helpers.ts b/examples/collaboration/tests/editor-helpers.ts new file mode 100644 index 0000000..2895ca8 --- /dev/null +++ b/examples/collaboration/tests/editor-helpers.ts @@ -0,0 +1,27 @@ +import { Page } from "@playwright/test"; +import { expect } from "./fixtures.ts"; + +export const editor = (page: Page) => page.getByRole("textbox", { name: "Shared document" }); + +export async function documentText(page: Page) { + return editor(page).locator(".cm-line").evaluateAll((lines: any[]) => lines.map((line: { cloneNode: (arg0: boolean) => any; }) => { + const copy = line.cloneNode(true); + copy.querySelectorAll(".cm-ySelectionCaret, .cm-placeholder").forEach((node: { remove: () => any; }) => node.remove()); + return copy.textContent; + }).join("\n")); +} + +export async function expectText(page: Page, value: unknown) { + if (value instanceof RegExp) await expect.poll(() => documentText(page)).toMatch(value); + else await expect.poll(() => documentText(page)).toBe(value); +} + +export async function insertAtStart(page: Page, value: string) { + await editor(page).click(); + await editor(page).press("ControlOrMeta+Home"); + await page.keyboard.insertText(value); +} + +export async function connected(page: Page) { + await expect(page.locator("#status")).toHaveText("Connected"); +} diff --git a/examples/collaboration/tests/failures.spec.js b/examples/collaboration/tests/failures.spec.ts similarity index 96% rename from examples/collaboration/tests/failures.spec.js rename to examples/collaboration/tests/failures.spec.ts index 76b95f5..d06589f 100644 --- a/examples/collaboration/tests/failures.spec.js +++ b/examples/collaboration/tests/failures.spec.ts @@ -1,11 +1,11 @@ -import { openRoom } from "./access-helpers.js"; +import { openRoom } from "./access-helpers.ts"; import { randomUUID } from "node:crypto"; -import { test, expect } from "./fixtures.js"; -import { editor, expectText, insertAtStart, connected } from "./editor-helpers.js"; +import { test, expect } from "./fixtures.ts"; +import { editor, expectText, insertAtStart, connected } from "./editor-helpers.ts"; // Phoenix's binary push header contains the join ref, request ref, topic and // event. Requests still reach the real backend; only their replies are held. -function binaryPush(message) { +function binaryPush(message: string|number[]|Buffer|[any,any,any,any,any]) { if (!Buffer.isBuffer(message) || message[0] !== 0) return null; const [, joinSize, refSize, topicSize, eventSize] = message; const refStart = 5 + joinSize; @@ -22,7 +22,7 @@ test("missing save acknowledgements never show Saved and reconnect confirms the await page.routeWebSocket("**/socket/websocket**", client => { const server = client.connectToServer(); const refs = new Set(); - const held = []; + const held: string[] = []; releaseReplies = () => { held.splice(0).forEach(message => client.send(message)); }; client.onMessage(message => { const push = binaryPush(message); diff --git a/examples/collaboration/tests/fixtures.js b/examples/collaboration/tests/fixtures.ts similarity index 80% rename from examples/collaboration/tests/fixtures.js rename to examples/collaboration/tests/fixtures.ts index 951ab93..abedc4d 100644 --- a/examples/collaboration/tests/fixtures.js +++ b/examples/collaboration/tests/fixtures.ts @@ -1,4 +1,4 @@ -import { test as base, expect } from "@playwright/test"; +import { test as base, expect, type Page } from "@playwright/test"; import { spawn, execFileSync } from "node:child_process"; import { once } from "node:events"; import { fileURLToPath } from "node:url"; @@ -7,10 +7,11 @@ const cwd = fileURLToPath(new URL("../../..", import.meta.url)); const env = { ...process.env, MIX_ENV: "test", PHX_SERVER: "true", PORT: "4010", SYNIXIR_BROWSER_TEST: "true" }; const endpoint = "http://127.0.0.1:4010/robots.txt"; -export const test = base.extend({ +interface Backend { logs(): string; restart(): Promise; compact(room: string): string } +export const test = base.extend<{ pageErrors: void }, { backend: Backend }>({ pageErrors: [async ({ context }, use) => { - const errors = []; - const listen = page => page.on("pageerror", error => errors.push(error.message)); + const errors: string[] = []; + const listen = (page: Page) => page.on("pageerror", error => errors.push(error.message)); context.pages().forEach(listen); context.on("page", listen); await use(); @@ -19,8 +20,8 @@ export const test = base.extend({ backend: [async ({}, use) => { execFileSync("mix", ["ecto.create", "--quiet"], { cwd, env }); execFileSync("mix", ["ecto.migrate", "--quiet"], { cwd, env }); - let child; - let exited; + let child: ReturnType; + let exited: Promise; let output = ""; async function ready() { @@ -37,7 +38,7 @@ export const test = base.extend({ child = spawn("mix", ["phx.server"], { cwd, env, detached: true, stdio: ["ignore", "pipe", "pipe"] }); exited = once(child, "exit"); for (const stream of [child.stdout, child.stderr]) { - stream.on("data", chunk => { output = (output + chunk).slice(-12000); }); + stream!.on("data", (chunk: string) => { output = (output + chunk).slice(-12000); }); } await expect.poll(async () => { if (child.exitCode !== null) throw new Error(output); @@ -49,7 +50,7 @@ export const test = base.extend({ if (child && child.exitCode === null && child.signalCode === null) { // Kill only the process group this fixture created. No shutdown hooks // can save the document on behalf of the implementation under test. - process.kill(-child.pid, "SIGKILL"); + process.kill(-child.pid!, "SIGKILL"); await exited; } } @@ -59,10 +60,10 @@ export const test = base.extend({ await use({ logs: () => output, restart: async () => { await stop(); await start(); }, - compact: room => { + compact: (room) => { // Use the production store against committed test data. Pass the room // through the environment so it can never become executable code. - const maintenanceEnv = { ...env, SYNIXIR_TEST_ROOM: room }; + const maintenanceEnv: NodeJS.ProcessEnv = { ...env, SYNIXIR_TEST_ROOM: room }; delete maintenanceEnv.PHX_SERVER; return execFileSync("mix", ["run", "-e", ` room = System.fetch_env!("SYNIXIR_TEST_ROOM") diff --git a/examples/collaboration/tests/flowchart.spec.js b/examples/collaboration/tests/flowchart.spec.ts similarity index 87% rename from examples/collaboration/tests/flowchart.spec.js rename to examples/collaboration/tests/flowchart.spec.ts index cdce052..00df6d6 100644 --- a/examples/collaboration/tests/flowchart.spec.js +++ b/examples/collaboration/tests/flowchart.spec.ts @@ -1,25 +1,26 @@ import { randomUUID } from "node:crypto"; -import { test, expect } from "./fixtures.js"; -import { api, register } from "./access-helpers.js"; +import { test, expect } from "./fixtures.ts"; +import { api, register } from "./access-helpers.ts"; +import { Locator, Page } from "@playwright/test"; -async function setup(page, baseURL) { +async function setup(page: Page, baseURL: string|undefined) { const user = await register(page.request, baseURL); const roomId = `flowchart-${randomUUID()}`; expect((await api(page.request, baseURL, "/api/rooms", "POST", { room_id: roomId })).ok()).toBe(true); return { user, roomId, url: `${baseURL}/flowchart.html?room=${roomId}` }; } -const saved = page => expect(page.locator("#save-status")).toHaveText("Saved"); -const node = (page, name) => page.getByRole("button", { name, exact: true }); -const position = locator => locator.evaluate(element => { +const saved = (page: Page) => expect(page.locator("#save-status")).toHaveText("Saved"); +const node = (page: Page, name: string) => page.getByRole("button", { name, exact: true }); +const position = (locator: Locator) => locator.evaluate((element: Element) => { const t = new DOMMatrixReadOnly(getComputedStyle(element).transform); return { x: t.m41, y: t.m42 }; }); -async function add(page, kind, label) { +async function add(page: Page, kind: string, label: string) { await page.locator(`[data-add="${kind}"]`).click(); await page.getByLabel("Node text", { exact: true }).fill(label); await saved(page); } -async function connect(page, from, to, label) { +async function connect(page: Page, from: string, to: string, label: string) { await node(page, from).click(); await page.getByLabel("Connect to", { exact: true }).selectOption({ label: to }); await page.getByLabel("Branch label", { exact: true }).fill(label); @@ -44,12 +45,13 @@ test("flowchart shares branches, smooth drags, cursors, selections, and local un const item = node(page, "Decision: Approved?"); await item.scrollIntoViewIfNeeded(); const before = await position(item), box = await item.boundingBox(); + if (!box) throw new Error("Expected a visible element"); const pathBefore = await peer.locator(".flow-edge").first().getAttribute("d"); await page.mouse.move(box.x + 100, box.y + 80); await page.mouse.down(); await page.mouse.move(box.x + 180, box.y + 140, { steps: 12 }); await expect.poll(() => position(node(peer, "Decision: Approved?"))).toEqual({ x: before.x + 80, y: before.y + 60 }); await expect(peer.locator(".remote-cursor")).toContainText(user.username); - await expect(peer.locator(".flow-edge").first()).not.toHaveAttribute("d", pathBefore); + await expect(peer.locator(".flow-edge").first()).not.toHaveAttribute("d", pathBefore!); await page.mouse.up(); await saved(page); await page.getByRole("button", { name: "Undo", exact: true }).click(); await expect.poll(() => position(node(peer, "Decision: Approved?"))).toEqual(before); @@ -116,7 +118,7 @@ test("flowchart respects viewer permissions and live downgrades on mobile", asyn const context = await browser.newContext({ viewport: { width: 390, height: 844 } }); try { const viewer = await context.newPage(); const user = await register(viewer.request, baseURL); - const role = async value => expect((await api(page.request, baseURL, `/api/rooms/${roomId}/members/${user.username}`, "PUT", { role: value })).ok()).toBe(true); + const role = async (value: string) => expect((await api(page.request, baseURL, `/api/rooms/${roomId}/members/${user.username}`, "PUT", { role: value })).ok()).toBe(true); await role("viewer"); await viewer.goto(url); await expect(viewer.locator("#save-status")).toHaveText("View only"); await expect(viewer.locator('[data-add="process"]')).toBeDisabled(); @@ -146,14 +148,15 @@ test("flowchart arrows stay attached on every interpolated drag frame and revers const peer = await context.newPage(); await peer.goto(url); await saved(peer); const item = node(page, "Process: Draft"); await item.scrollIntoViewIfNeeded(); const box = await item.boundingBox(); + if (!box) throw new Error("Expected a visible element"); await page.mouse.move(box.x + 50, box.y + 40); await page.mouse.down(); await peer.evaluate(() => { - const shape = document.querySelector('.flow-node'); - const arrow = document.querySelector('.flow-edge'); + const shape = document.querySelector('.flow-node')!; + const arrow = document.querySelector('.flow-edge')!; const initial = new DOMMatrixReadOnly(getComputedStyle(shape).transform).m42; window.flowMotion = new Promise(resolve => { - const frames = [], start = performance.now(); - function sample(time) { + const frames: { y: number; separation: number }[] = [], start = performance.now(); + function sample(time: number) { const y = new DOMMatrixReadOnly(getComputedStyle(shape).transform).m42; const point = arrow.getPointAtLength(0); frames.push({ y: y - initial, separation: Math.abs(point.y - y - shape.offsetHeight / 2) }); @@ -164,12 +167,13 @@ test("flowchart arrows stay attached on every interpolated drag frame and revers }); await page.mouse.move(box.x + 50, box.y + 100); const frames = await peer.evaluate(() => window.flowMotion); - expect(frames.filter(frame => frame.y > 1 && frame.y < 59).length).toBeGreaterThanOrEqual(2); - expect(Math.max(...frames.map(frame => frame.separation))).toBeLessThan(1); + expect(frames.filter((frame: { y: number; }) => frame.y > 1 && frame.y < 59).length).toBeGreaterThanOrEqual(2); + expect(Math.max(...frames.map((frame: { separation: any; }) => frame.separation))).toBeLessThan(1); await page.mouse.up(); await saved(page); await connect(page, "Start / End: Done", "Draft", "Retry"); await expect(peer.locator('.flow-edge')).toHaveCount(2); - const curves = await peer.locator('.flow-edge').evaluateAll(paths => paths.map(path => { + const curves = await peer.locator('.flow-edge').evaluateAll(paths => paths.map(element => { + const path = element as SVGPathElement; const point = path.getPointAtLength(path.getTotalLength() / 2); return { x: point.x, y: point.y }; })); expect(Math.hypot(curves[0].x - curves[1].x, curves[0].y - curves[1].y)).toBeGreaterThan(50); diff --git a/examples/collaboration/tests/kanban.spec.js b/examples/collaboration/tests/kanban.spec.ts similarity index 90% rename from examples/collaboration/tests/kanban.spec.js rename to examples/collaboration/tests/kanban.spec.ts index 7674382..8a6d7db 100644 --- a/examples/collaboration/tests/kanban.spec.js +++ b/examples/collaboration/tests/kanban.spec.ts @@ -1,17 +1,18 @@ import { randomUUID } from "node:crypto"; -import { test, expect } from "./fixtures.js"; -import { api, register } from "./access-helpers.js"; +import { test, expect } from "./fixtures.ts"; +import { api, register } from "./access-helpers.ts"; +import { Page } from "@playwright/test"; -async function setup(page, baseURL) { +async function setup(page: Page, baseURL: string|undefined) { const user = await register(page.request, baseURL); const roomId = `board-${randomUUID()}`; const response = await api(page.request, baseURL, "/api/rooms", "POST", { room_id: roomId }); expect(response.ok()).toBe(true); return { user, roomId, url: `${baseURL}/kanban.html?room=${roomId}` }; } -const saved = page => expect(page.locator("#save-status")).toHaveText("Saved"); -const card = (page, title) => page.getByRole("button", { name: `Open card: ${title}`, exact: true }); -async function addCard(page, title) { +const saved = (page: Page) => expect(page.locator("#save-status")).toHaveText("Saved"); +const card = (page: Page, title: string) => page.getByRole("button", { name: `Open card: ${title}`, exact: true }); +async function addCard(page: Page, title: string) { await page.getByRole("button", { name: "Add card to Backlog", exact: true }).click(); await page.getByLabel("Title", { exact: true }).fill(title); await page.getByRole("button", { name: "Close card", exact: true }).click(); @@ -33,7 +34,9 @@ test("cards sync, show presence, move by drag, and undo preserves a teammate's e await expect(page.locator(".card-presence")).toContainText(user.username); await peer.getByRole("button", { name: "Close card", exact: true }).click(); const grip = await page.locator(".card-grip").boundingBox(); + if (!grip) throw new Error("Expected a visible element"); const destination = await page.locator('[data-column="in-progress"] .column-heading').boundingBox(); + if (!destination) throw new Error("Expected a visible element"); // Use a gradual pointer gesture into the visible header. Scrolling the full // tall column into view mid-gesture can cancel native HTML dragging. await page.mouse.move(grip.x + grip.width / 2, grip.y + grip.height / 2); @@ -95,7 +98,7 @@ test("viewers can inspect cards but cannot edit, and revocation freezes an open try { const viewer = await viewerContext.newPage(); const user = await register(viewer.request, baseURL); - const setRole = async role => expect((await api(page.request, baseURL, + const setRole = async (role: string) => expect((await api(page.request, baseURL, `/api/rooms/${roomId}/members/${user.username}`, "PUT", { role })).ok()).toBe(true); await setRole("viewer"); await viewer.goto(url); @@ -131,8 +134,8 @@ test("board navigation stays on Kanban and the layout fits mobile", async ({ pag await page.getByRole("button", { name: "Close card", exact: true }).click(); await expect(page.locator('[data-column="done"]')).toContainText("Mobile planning"); expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true); - await expect(page.getByRole("link", { name: "Try the text editor" })).toHaveAttribute("href", `${baseURL}/?room=${roomId}`); - await page.getByRole("link", { name: "All boards" }).click(); + await expect(page.getByRole("link", { name: "Try the text editor" })).toHaveJSProperty("href", `${baseURL}/?room=${roomId}`); + await page.getByRole("link", { name: "All rooms" }).click(); await expect(page).toHaveURL(`${baseURL}/kanban.html`); await page.getByRole("link", { name: `${roomId} · owner`, exact: true }).click(); await saved(page); diff --git a/examples/collaboration/tests/lifecycle.spec.js b/examples/collaboration/tests/lifecycle.spec.ts similarity index 87% rename from examples/collaboration/tests/lifecycle.spec.js rename to examples/collaboration/tests/lifecycle.spec.ts index 5ef6d76..e223860 100644 --- a/examples/collaboration/tests/lifecycle.spec.js +++ b/examples/collaboration/tests/lifecycle.spec.ts @@ -1,28 +1,27 @@ -import { openRoom } from "./access-helpers.js"; +import { openRoom } from "./access-helpers.ts"; import { createHash, randomUUID } from "node:crypto"; -import { test, expect } from "./fixtures.js"; -import { connected, editor } from "./editor-helpers.js"; +import { test, expect } from "./fixtures.ts"; +import { connected, editor } from "./editor-helpers.ts"; +import { Page } from "@playwright/test"; // CodeMirror virtualizes large documents. Read its public state instead of the // visible DOM, and compare hashes so a failed assertion cannot dump megabytes. -async function expectDocument(page, expected) { +async function expectDocument(page: Page, expected: string) { const digest = createHash("sha256").update(expected).digest("hex"); await expect.poll(() => page.evaluate(async () => { - const { EditorView } = await import("/node_modules/.vite/deps/@codemirror_view.js"); - const text = EditorView.findFromDOM(document.querySelector(".cm-editor")).state.doc.toString(); + const text = window.synixirTest.editor!.state.doc.toString(); const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(text)); return Array.from(new Uint8Array(digest), byte => byte.toString(16).padStart(2, "0")).join(""); })).toBe(digest); } -async function edit(page, changes) { - await page.evaluate(async changes => { - const { EditorView } = await import("/node_modules/.vite/deps/@codemirror_view.js"); - EditorView.findFromDOM(document.querySelector(".cm-editor")).dispatch({ changes }); +async function edit(page: Page, changes: { from: number; to?: number; insert?: string } | { from: number; to?: number; insert?: string }[]) { + await page.evaluate(async (changes) => { + window.synixirTest.editor!.dispatch({ changes }); }, changes); } -function binaryPush(message) { +function binaryPush(message: string|number[]|Buffer|[any,any,any,any,any]) { if (!Buffer.isBuffer(message) || message[0] !== 0) return null; const [, joinSize, refSize, topicSize, eventSize] = message; const eventStart = 5 + joinSize + refSize + topicSize; @@ -33,7 +32,7 @@ function binaryPush(message) { }; } -function chunk(push) { +function chunk(push: { ref: string; event: string; payload: Buffer; }|null) { if (push?.event !== "transfer_chunk") return null; const payload = push.payload; return { kind: payload[0], offset: payload.readUInt32BE(5), total: payload.readUInt32BE(9), @@ -117,8 +116,8 @@ test("an interrupted chunk upload is never saved and reconnect retries the retai test("a missing final chunk acknowledgement stays unconfirmed and stale replies cannot save a new generation", async ({ page, baseURL }) => { const url = `${baseURL}/?room=chunk-ack-${randomUUID()}`; let hold = true; - const held = []; - const release = []; + const held: string[] = []; + const release: (() => void)[] = []; await page.routeWebSocket("**/socket/websocket**", client => { const server = client.connectToServer(); const refs = new Set(); @@ -126,7 +125,7 @@ test("a missing final chunk acknowledgement stays unconfirmed and stale replies client.onMessage(message => { const push = binaryPush(message); const part = chunk(push); - if (hold && part?.kind === 2 && part.final) refs.add(push.ref); + if (hold && part?.kind === 2 && part.final) refs.add(push!.ref); server.send(message); }); server.onMessage(message => { diff --git a/examples/collaboration/tests/multiplayer-form.spec.js b/examples/collaboration/tests/multiplayer-form.spec.ts similarity index 89% rename from examples/collaboration/tests/multiplayer-form.spec.js rename to examples/collaboration/tests/multiplayer-form.spec.ts index ebafe54..284066f 100644 --- a/examples/collaboration/tests/multiplayer-form.spec.js +++ b/examples/collaboration/tests/multiplayer-form.spec.ts @@ -1,16 +1,17 @@ import { randomUUID } from "node:crypto"; -import { test, expect } from "./fixtures.js"; -import { api, register } from "./access-helpers.js"; +import { test, expect } from "./fixtures.ts"; +import { api, register } from "./access-helpers.ts"; +import { Locator, Page } from "@playwright/test"; -const field = (page, name) => page.getByRole("textbox", { name, exact: true }); -const saved = page => expect(page.locator("#save-status")).toHaveText("Saved"); -const value = locator => locator.locator(".cm-line").evaluateAll(lines => lines.map(line => { +const field = (page: Page, name: string) => page.getByRole("textbox", { name, exact: true }); +const saved = (page: Page) => expect(page.locator("#save-status")).toHaveText("Saved"); +const value = (locator: Locator) => locator.locator(".cm-line").evaluateAll((lines: any[]) => lines.map((line: { cloneNode: (arg0: boolean) => any; }) => { const copy = line.cloneNode(true); - copy.querySelectorAll(".cm-ySelectionCaret, .cm-placeholder").forEach(node => node.remove()); + copy.querySelectorAll(".cm-ySelectionCaret, .cm-placeholder").forEach((node: { remove: () => any; }) => node.remove()); return copy.textContent; }).join("\n")); -const hasValue = (page, name, text) => expect.poll(() => value(field(page, name))).toBe(text); -async function setup(page, baseURL) { +const hasValue = (page: Page, name: string, text: unknown) => expect.poll(() => value(field(page, name))).toBe(text); +async function setup(page: Page, baseURL: string|undefined) { const user = await register(page.request, baseURL); const roomId = `form-${randomUUID()}`; expect((await api(page.request, baseURL, "/api/rooms", "POST", { room_id: roomId })).ok()).toBe(true); @@ -19,7 +20,7 @@ async function setup(page, baseURL) { await saved(page); return { user, roomId, url }; } -async function fillBrief(page) { +async function fillBrief(page: Page) { await field(page, "Project name").fill("A shared launch"); await field(page, "What are we making?").fill("A clearer way to plan together."); await field(page, "Who is it for?").fill("Small product teams."); @@ -125,7 +126,7 @@ test("form viewers and revoked editors cannot mutate fields or use history; mobi try { const viewer = await viewerContext.newPage(); const user = await register(viewer.request, baseURL); - const role = async value => expect((await api(page.request, baseURL, `/api/rooms/${roomId}/members/${user.username}`, "PUT", { role: value })).ok()).toBe(true); + const role = async (value: string) => expect((await api(page.request, baseURL, `/api/rooms/${roomId}/members/${user.username}`, "PUT", { role: value })).ok()).toBe(true); await role("viewer"); await viewer.goto(url); await expect(viewer.locator("#save-status")).toHaveText("View only"); diff --git a/examples/collaboration/tests/permissions.spec.js b/examples/collaboration/tests/permissions.spec.ts similarity index 95% rename from examples/collaboration/tests/permissions.spec.js rename to examples/collaboration/tests/permissions.spec.ts index 28c1894..ce67415 100644 --- a/examples/collaboration/tests/permissions.spec.js +++ b/examples/collaboration/tests/permissions.spec.ts @@ -1,17 +1,18 @@ import { randomUUID } from "node:crypto"; -import { test, expect } from "./fixtures.js"; -import { api, register, password } from "./access-helpers.js"; -import { connected, editor, expectText, insertAtStart } from "./editor-helpers.js"; +import { test, expect } from "./fixtures.ts"; +import { api, register, password } from "./access-helpers.ts"; +import { connected, editor, expectText, insertAtStart } from "./editor-helpers.ts"; +import { Page } from "@playwright/test"; -const username = prefix => `${prefix}_${randomUUID().replaceAll("-", "").slice(0, 20)}`; -async function signIn(page, name, button = "Sign in") { +const username = (prefix: string) => `${prefix}_${randomUUID().replaceAll("-", "").slice(0, 20)}`; +async function signIn(page: Page, name: string, button = "Sign in") { await page.getByLabel("Username", { exact: true }).fill(name); await page.getByLabel("Password", { exact: true }).fill(password); await page.getByRole("button", { name: button, exact: true }).click(); } test("owners manage private rooms and viewers cannot edit", async ({ page, browser, baseURL }) => { - await page.goto(baseURL); + await page.goto(baseURL!); await signIn(page, username("owner"), "Create account"); await expect(page.getByRole("heading", { name: "Your rooms", exact: true })).toBeVisible(); const room = `private-${randomUUID()}`; @@ -73,7 +74,7 @@ test("signing out clears every tab before a different account signs in", async ( await page.getByRole("button", { name: "Sign out", exact: true }).click(); for (const tab of [page, second]) { await expect(tab.locator("#auth-panel")).toBeVisible(); - await expect(tab.locator("#editor")).toBeEmpty(); + await expect(tab.locator("#editor")).toHaveCount(0); } await signIn(page, username("other"), "Create account"); await expect(page.locator("#status")).toHaveText("Access expired or denied"); diff --git a/examples/collaboration/tests/placeholder-cursors.spec.js b/examples/collaboration/tests/placeholder-cursors.spec.ts similarity index 88% rename from examples/collaboration/tests/placeholder-cursors.spec.js rename to examples/collaboration/tests/placeholder-cursors.spec.ts index 5adbed8..7954087 100644 --- a/examples/collaboration/tests/placeholder-cursors.spec.js +++ b/examples/collaboration/tests/placeholder-cursors.spec.ts @@ -1,6 +1,6 @@ import { randomUUID } from "node:crypto"; -import { test, expect } from "./fixtures.js"; -import { api, register } from "./access-helpers.js"; +import { test, expect } from "./fixtures.ts"; +import { api, register } from "./access-helpers.ts"; for (const [path, name] of [ ["/", "Shared document"], @@ -23,8 +23,8 @@ for (const [path, name] of [ await expect(target.locator(".cm-placeholder")).toBeVisible(); await expect(target.locator(".cm-ySelectionCaret")).toHaveCount(1); const offset = () => target.evaluate(node => { - const caret = node.querySelector(".cm-ySelectionCaret").getBoundingClientRect(); - const placeholder = node.querySelector(".cm-placeholder").getBoundingClientRect(); + const caret = node.querySelector(".cm-ySelectionCaret")!.getBoundingClientRect(); + const placeholder = node.querySelector(".cm-placeholder")!.getBoundingClientRect(); return { x: Math.abs(caret.left - placeholder.left), y: Math.abs(caret.top - placeholder.top) }; }); const before = await offset(); @@ -60,7 +60,7 @@ test("empty rich text keeps its remote caret at the paragraph start", async ({ p await expect(target).toHaveAttribute("data-empty", "true"); await expect(target.locator(".rich-caret")).toHaveCount(1); const offset = () => target.evaluate(node => Math.abs( - node.querySelector(".rich-caret").getBoundingClientRect().left - node.querySelector("p").getBoundingClientRect().left)); + node.querySelector(".rich-caret")!.getBoundingClientRect().left - node.querySelector("p")!.getBoundingClientRect().left)); await expect.poll(offset).toBeLessThan(3); await peer.setViewportSize({ width: 390, height: 844 }); await expect.poll(offset).toBeLessThan(3); diff --git a/examples/collaboration/tests/rich-text.spec.js b/examples/collaboration/tests/rich-text.spec.ts similarity index 86% rename from examples/collaboration/tests/rich-text.spec.js rename to examples/collaboration/tests/rich-text.spec.ts index ed9b228..e013439 100644 --- a/examples/collaboration/tests/rich-text.spec.js +++ b/examples/collaboration/tests/rich-text.spec.ts @@ -1,16 +1,17 @@ import { randomUUID } from "node:crypto"; -import { test, expect } from "./fixtures.js"; -import { api, register } from "./access-helpers.js"; +import { test, expect } from "./fixtures.ts"; +import { api, register } from "./access-helpers.ts"; +import { Page } from "@playwright/test"; -const document = page => page.getByRole("textbox", { name: "Rich text document", exact: true }); -const textWithoutCursors = locator => locator.evaluate(node => { +const document = (page: Page) => page.getByRole("textbox", { name: "Rich text document", exact: true }); +const textWithoutCursors = (locator: { evaluate: (arg0: (node: any) => any) => any; }) => locator.evaluate((node: { cloneNode: (arg0: boolean) => any; }) => { const copy = node.cloneNode(true); - copy.querySelectorAll(".rich-caret").forEach(caret => caret.remove()); + copy.querySelectorAll(".rich-caret").forEach((caret: { remove: () => any; }) => caret.remove()); return copy.textContent; }); -const content = (locator, text) => expect.poll(() => textWithoutCursors(locator)).toBe(text); -const saved = page => expect(page.locator("#save-status")).toHaveText("Saved"); -async function setup(page, baseURL) { +const content = (locator: any, text: unknown) => expect.poll(() => textWithoutCursors(locator)).toBe(text); +const saved = (page: Page) => expect(page.locator("#save-status")).toHaveText("Saved"); +async function setup(page: Page, baseURL: string|undefined) { const user = await register(page.request, baseURL); const roomId = `rich-text-${randomUUID()}`; expect((await api(page.request, baseURL, "/api/rooms", "POST", { room_id: roomId })).ok()).toBe(true); @@ -19,7 +20,7 @@ async function setup(page, baseURL) { await saved(page); return { user, roomId, url }; } -async function selectAll(page) { +async function selectAll(page: Page) { await document(page).click(); await page.keyboard.press("ControlOrMeta+a"); } @@ -115,7 +116,7 @@ test("rich text supports lists and quotes and keeps plain text content and curso await expect(document(page).locator(".rich-caret")).toHaveCount(0); await document(page).click(); await expect(plain.locator(".cm-ySelectionInfo")).toHaveCount(0); - await expect(page.getByRole("link", { name: "Try the text editor", exact: true })).toHaveAttribute("href", `${baseURL}/?room=${roomId}`); + await expect(page.getByRole("link", { name: "Try the text editor", exact: true })).toHaveJSProperty("href", `${baseURL}/?room=${roomId}`); }); test("rich text viewer and revoked editor cannot edit, format, or undo; mobile fits", async ({ page, browser, baseURL }) => { @@ -126,14 +127,14 @@ test("rich text viewer and revoked editor cannot edit, format, or undo; mobile f try { const viewer = await viewerContext.newPage(); const user = await register(viewer.request, baseURL); - const role = async value => expect((await api(page.request, baseURL, `/api/rooms/${roomId}/members/${user.username}`, "PUT", { role: value })).ok()).toBe(true); + const role = async (value: string) => expect((await api(page.request, baseURL, `/api/rooms/${roomId}/members/${user.username}`, "PUT", { role: value })).ok()).toBe(true); await role("viewer"); await viewer.goto(url); await expect(viewer.locator("#save-status")).toHaveText("View only"); await expect(document(viewer)).not.toBeEditable(); await expect(viewer.getByRole("button", { name: "Bold", exact: true })).toBeDisabled(); await content(document(viewer), "Review this draft"); - expect(await viewer.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true); + expect(await viewer.evaluate(() => window.document.documentElement.scrollWidth <= innerWidth)).toBe(true); await role("editor"); await viewer.reload(); await saved(viewer); diff --git a/examples/collaboration/tests/sdk.spec.js b/examples/collaboration/tests/sdk.spec.ts similarity index 80% rename from examples/collaboration/tests/sdk.spec.js rename to examples/collaboration/tests/sdk.spec.ts index 99d550d..5577584 100644 --- a/examples/collaboration/tests/sdk.spec.js +++ b/examples/collaboration/tests/sdk.spec.ts @@ -1,17 +1,18 @@ import { randomUUID } from "node:crypto"; -import { test, expect } from "./fixtures.js"; -import { api, register } from "./access-helpers.js"; +import { test, expect } from "./fixtures.ts"; +import { api, register } from "./access-helpers.ts"; +import { Page } from "@playwright/test"; -async function setup(page, baseURL) { +async function setup(page: Page, baseURL: string|undefined) { const user = await register(page.request, baseURL); const roomId = `sdk-${randomUUID()}`; const response = await api(page.request, baseURL, "/api/rooms", "POST", { room_id: roomId }); expect(response.ok()).toBe(true); return { user, roomId, url: `${baseURL}/sdk.html?room=${roomId}` }; } -const saved = page => expect(page.locator("#sdk-state")).toHaveText("connected · saved"); +const saved = (page: Page) => expect(page.locator("#sdk-state")).toHaveText("connected · saved"); -function binaryPush(message) { +function binaryPush(message: string|number[]|Buffer|[any,any,any,any,any]) { if (!Buffer.isBuffer(message) || message[0] !== 0) return null; const [, joinSize, refSize, topicSize, eventSize] = message; const eventStart = 5 + joinSize + refSize + topicSize; @@ -23,14 +24,14 @@ function binaryPush(message) { for (const replyTiming of ["before release", "after release"]) { test(`public SDK sync readiness precedes durability with a reply ${replyTiming}`, async ({ page, context, baseURL }) => { const { url } = await setup(page, baseURL); - const releases = []; - const replyArrived = Promise.withResolvers(); - const deliverReply = Promise.withResolvers(); - const replyHeld = Promise.withResolvers(); + const releases: (() => void)[] = []; + const replyArrived = Promise.withResolvers(); + const deliverReply = Promise.withResolvers(); + const replyHeld = Promise.withResolvers(); await page.routeWebSocket("**/socket/websocket**", client => { const server = client.connectToServer(); const refs = new Set(); - const held = []; + const held: string[] = []; let holding = true; releases.push(() => { holding = false; @@ -59,8 +60,8 @@ for (const replyTiming of ["before release", "after release"]) { }); await page.goto(url); await expect(page.locator("#sdk-state")).toHaveText("connected · saving"); - // sdk.js awaits connect(): importing its exports completes while saves are held. - expect(await page.evaluate(async () => (await import("/sdk.js")).room.state.saveStatus)).toBe("saving"); + // Synchronization is ready while the durable save acknowledgement is held. + expect(await page.evaluate(async () => window.synixirTest.room!.state.saveStatus)).toBe("saving"); const reply = JSON.parse(await replyArrived.promise); expect(reply[4]).toMatchObject({ status: "ok", response: { saved: true } }); // Exercise both arrival orders without depending on runner or network speed. @@ -93,7 +94,7 @@ test("fresh grants recover a bare SDK after a crash and overlapping disconnect/c await expect.poll(() => grants).toBeGreaterThan(beforeRestart); await saved(page); const result = await page.evaluate(async () => { - const { room } = await import("/sdk.js"); + const room = window.synixirTest.room!; const closing = room.disconnect(); room.doc.getMap("settings").set("title", "Retained offline edit"); const first = room.connect(); @@ -109,18 +110,18 @@ test("fresh grants recover a bare SDK after a crash and overlapping disconnect/c await expect(peer.getByLabel("Room title")).toHaveValue("Retained offline edit"); const beforeDestroy = grants; const final = await page.evaluate(async () => { - const { room } = await import("/sdk.js"); - const connections = []; - room.subscribe(state => connections.push(state.connection)); + const room = window.synixirTest.room!; + const connections: any[] = []; + room.subscribe((state: { connection: any; }) => connections.push(state.connection)); await room.destroy(); const count = connections.length; await room.destroy(); - const error = await room.connect().catch(error => error.code); + const error = await room.connect().catch((error: { code: any; }) => error.code); return { count, finalCount: connections.length, last: connections.at(-1), error, destroyed: room.doc.isDestroyed }; }); expect(final.count).toBe(final.finalCount); expect(final).toMatchObject({ last: "destroyed", error: "destroyed", destroyed: true }); - await expect.poll(() => peer.evaluate(async () => (await import("/sdk.js")).room.awareness.getStates().size)).toBe(1); + await expect.poll(() => peer.evaluate(async () => window.synixirTest.room!.awareness.getStates().size)).toBe(1); expect(grants).toBe(beforeDestroy); }); @@ -132,7 +133,7 @@ test("viewer transport suppresses programmatic document writes while keeping awa const user = await register(viewer.request, baseURL); const response = await api(page.request, baseURL, `/api/rooms/${roomId}/members/${user.username}`, "PUT", { role: "viewer" }); expect(response.ok()).toBe(true); - const writes = []; + const writes: any[] = []; viewer.on("websocket", ws => ws.on("framesent", ({ payload }) => { const push = binaryPush(payload); if (push?.event === "save_update" || push?.event === "transfer_chunk" || @@ -143,14 +144,14 @@ test("viewer transport suppresses programmatic document writes while keeping awa await viewer.goto(url); await expect(viewer.locator("#sdk-state")).toHaveText("connected · view-only"); await viewer.evaluate(async () => { - const { room } = await import("/sdk.js"); + const room = window.synixirTest.room!; room.doc.getMap("settings").set("title", "local viewer mutation"); room.awareness.setLocalStateField("page", "viewer-presence"); }); - await expect.poll(() => page.evaluate(async () => [...(await import("/sdk.js")).room.awareness.getStates().values()] + await expect.poll(() => page.evaluate(async () => [...window.synixirTest.room!.awareness.getStates().values()] .some(state => state.page === "viewer-presence"))).toBe(true); await viewer.evaluate(async () => { - const { room } = await import("/sdk.js"); + const room = window.synixirTest.room!; await room.disconnect(); await room.connect(); }); diff --git a/examples/collaboration/tests/table.spec.js b/examples/collaboration/tests/table.spec.ts similarity index 89% rename from examples/collaboration/tests/table.spec.js rename to examples/collaboration/tests/table.spec.ts index d0f863d..b085d1f 100644 --- a/examples/collaboration/tests/table.spec.js +++ b/examples/collaboration/tests/table.spec.ts @@ -1,24 +1,25 @@ import { randomUUID } from "node:crypto"; -import { test, expect } from "./fixtures.js"; -import { api, register } from "./access-helpers.js"; +import { test, expect } from "./fixtures.ts"; +import { api, register } from "./access-helpers.ts"; +import { Page } from "@playwright/test"; -async function setup(page, baseURL) { +async function setup(page: Page, baseURL: string|undefined) { const user = await register(page.request, baseURL); const roomId = `table-${randomUUID()}`; expect((await api(page.request, baseURL, "/api/rooms", "POST", { room_id: roomId })).ok()).toBe(true); return { user, roomId, url: `${baseURL}/table.html?room=${roomId}` }; } -const saved = page => expect(page.locator("#save-status")).toHaveText("Saved"); -const cell = (page, address) => page.getByRole("gridcell", { name: new RegExp(`^${address}:`) }); -const display = (page, address) => cell(page, address).locator(".cell-display"); -async function edit(page, address, value) { +const saved = (page: Page) => expect(page.locator("#save-status")).toHaveText("Saved"); +const cell = (page: Page, address: string) => page.getByRole("gridcell", { name: new RegExp(`^${address}:`) }); +const display = (page: Page, address: string) => cell(page, address).locator(".cell-display"); +async function edit(page: Page, address: string, value: string) { await cell(page, address).dblclick(); await page.getByRole("textbox", { name: `Edit ${address}`, exact: true }).fill(value); await page.keyboard.press("Escape"); await saved(page); } -async function paste(page, address, value) { +async function paste(page: Page, address: string, value: string) { await cell(page, address).click(); - await cell(page, address).evaluate((node, value) => { + await cell(page, address).evaluate((node: { dispatchEvent: (arg0: ClipboardEvent) => void; }, value: string) => { const clipboardData = new DataTransfer(); clipboardData.setData("text/plain", value); node.dispatchEvent(new ClipboardEvent("paste", { clipboardData, bubbles: true, cancelable: true })); }, value); @@ -33,11 +34,11 @@ test("table shares same-cell typing, remote carets and selections while preservi await expect(cell(peer, "A1").locator(".cell-people")).toContainText(user.username); await cell(peer, "A1").dblclick(); await expect(cell(peer, "A1").locator(".cm-ySelectionCaret")).toHaveCount(1); - await peer.evaluate(() => { window.originalCellEditor = document.querySelector('.cm-content'); }); + await peer.evaluate(() => { window.originalCellEditor = document.querySelector('.cm-content')!; }); await page.getByRole("textbox", { name: "Edit A1", exact: true }).press("End"); await page.keyboard.insertText("the launch"); await saved(page); await expect(display(peer, "A1")).toHaveText("Plan the launch"); - expect(await peer.evaluate(() => window.originalCellEditor === document.querySelector('.cm-content'))).toBe(true); + expect(await peer.evaluate(() => window.originalCellEditor === document.querySelector('.cm-content')!)).toBe(true); await peer.keyboard.press("Escape"); await edit(peer, "B1", "Mina"); await page.getByRole("button", { name: "Undo", exact: true }).click(); @@ -89,7 +90,7 @@ test("table merges disconnected edits to one cell and restores data and schema a await peer.getByLabel("Column name", { exact: true }).fill("Notes"); await saved(peer); await page.getByRole("button", { name: "Connect", exact: true }).click(); await saved(page); await expect(display(page, "A1")).toContainText("design"); await expect(display(page, "A1")).toContainText("build"); - const merged = await display(page, "A1").textContent(); + const merged = (await display(page, "A1").textContent())!; await expect(display(peer, "A1")).toHaveText(merged); await page.close(); await peer.close(); await backend.restart(); const fresh = await context.newPage(); await fresh.goto(url); await saved(fresh); @@ -105,7 +106,7 @@ test("table viewers cannot type, paste, delete or undo; live downgrades and mobi const context = await browser.newContext({ viewport: { width: 390, height: 844 } }); try { const viewer = await context.newPage(), user = await register(viewer.request, baseURL); - const role = async value => expect((await api(page.request, baseURL, `/api/rooms/${roomId}/members/${user.username}`, "PUT", { role: value })).ok()).toBe(true); + const role = async (value: string) => expect((await api(page.request, baseURL, `/api/rooms/${roomId}/members/${user.username}`, "PUT", { role: value })).ok()).toBe(true); await role("viewer"); await viewer.goto(url); await expect(viewer.locator("#save-status")).toHaveText("View only"); await expect(viewer.locator("#add-row")).toBeDisabled(); await cell(viewer, "A1").click(); await viewer.keyboard.press("Delete"); await paste(viewer, "A1", "Replace"); @@ -149,7 +150,7 @@ test("empty table carets align with the placeholder and stay scoped to their cel const peer = await context.newPage(); await peer.goto(url); await saved(peer); await cell(page, "A1").dblclick(); await cell(peer, "A1").dblclick(); await expect(cell(peer, "A1").locator(".cm-ySelectionCaret")).toHaveCount(1); - const gap = await cell(peer, "A1").evaluate(node => Math.abs(node.querySelector('.cm-ySelectionCaret').getBoundingClientRect().x - node.querySelector('.cm-placeholder').getBoundingClientRect().x)); + const gap = await cell(peer, "A1").evaluate((node) => Math.abs(node.querySelector('.cm-ySelectionCaret')!.getBoundingClientRect().x - node.querySelector('.cm-placeholder')!.getBoundingClientRect().x)); expect(gap).toBeLessThan(3); await peer.keyboard.press("Escape"); await cell(peer, "B1").dblclick(); await expect(cell(peer, "B1").locator(".cm-ySelectionCaret")).toHaveCount(0); diff --git a/examples/collaboration/tests/transport.spec.js b/examples/collaboration/tests/transport.spec.ts similarity index 96% rename from examples/collaboration/tests/transport.spec.js rename to examples/collaboration/tests/transport.spec.ts index aeb2df6..5fb15e1 100644 --- a/examples/collaboration/tests/transport.spec.js +++ b/examples/collaboration/tests/transport.spec.ts @@ -1,7 +1,7 @@ import { createConnection } from "node:net"; import { request } from "node:http"; import { randomBytes } from "node:crypto"; -import { test, expect } from "./fixtures.js"; +import { test, expect } from "./fixtures.ts"; test("the WebSocket handshake rejects an untrusted browser origin", async () => { const status = await new Promise((resolve, reject) => { @@ -28,7 +28,7 @@ test("the WebSocket handshake rejects an untrusted browser origin", async () => expect(status).toBe(403); }); -function frame(size, opcode, final) { +function frame(size: number, opcode: number, final: boolean) { const bytes = Buffer.alloc(14 + size); bytes[0] = (final ? 0x80 : 0) | opcode; bytes[1] = 0x80 | 127; diff --git a/examples/collaboration/tests/whiteboard-motion.spec.js b/examples/collaboration/tests/whiteboard-motion.spec.ts similarity index 77% rename from examples/collaboration/tests/whiteboard-motion.spec.js rename to examples/collaboration/tests/whiteboard-motion.spec.ts index 15cedbb..d1a1182 100644 --- a/examples/collaboration/tests/whiteboard-motion.spec.js +++ b/examples/collaboration/tests/whiteboard-motion.spec.ts @@ -1,6 +1,6 @@ import { randomUUID } from "node:crypto"; -import { test, expect } from "./fixtures.js"; -import { api, register } from "./access-helpers.js"; +import { test, expect } from "./fixtures.ts"; +import { api, register } from "./access-helpers.ts"; for (const reducedMotion of [false, true]) test(`remote cursor motion is smooth and stable, reduced motion=${reducedMotion}`, async ({ page, context, baseURL }) => { await register(page.request, baseURL); @@ -17,13 +17,14 @@ for (const reducedMotion of [false, true]) test(`remote cursor motion is smooth await expect(peer.locator("#save-status")).toHaveText("Saved"); await page.locator("#whiteboard-viewport").scrollIntoViewIfNeeded(); const box = await page.locator("#whiteboard-viewport").boundingBox(); + if (!box) throw new Error("Expected a visible element"); await page.mouse.move(box.x + 80, box.y + 80); await expect(peer.locator(".remote-cursor")).toHaveCount(1); await peer.evaluate(() => { - const pointer = document.querySelector(".remote-cursor"); - const participant = document.querySelector("#participants li:last-child"); + const pointer = document.querySelector(".remote-cursor")!; + const participant = document.querySelector("#participants li:last-child")!; const originalX = pointer.getBoundingClientRect().x; - const frames = []; + const frames: number[] = []; let unrelatedMutations = 0; const observer = new MutationObserver(records => { unrelatedMutations += records.length; }); for (const node of document.querySelectorAll(".whiteboard-object, .object-inspector, #participants")) { @@ -31,13 +32,13 @@ for (const reducedMotion of [false, true]) test(`remote cursor motion is smooth } window.motionSample = new Promise(resolve => { const started = performance.now(); - function frame(time) { - const current = document.querySelector(".remote-cursor"); + function frame(time: number) { + const current = document.querySelector(".remote-cursor")!; frames.push(current.getBoundingClientRect().x - originalX); if (time - started < 500) requestAnimationFrame(frame); else { observer.disconnect(); - resolve({ frames, unrelatedMutations, samePointer: current === pointer, sameParticipant: participant === document.querySelector("#participants li:last-child") }); + resolve({ frames, unrelatedMutations, samePointer: current === pointer, sameParticipant: participant === document.querySelector("#participants li:last-child")! }); } } requestAnimationFrame(frame); @@ -45,7 +46,7 @@ for (const reducedMotion of [false, true]) test(`remote cursor motion is smooth }); await page.mouse.move(box.x + 280, box.y + 80); const sample = await peer.evaluate(() => window.motionSample); - const intermediate = sample.frames.filter(x => x > 1 && x < 199); + const intermediate = sample.frames.filter((x: number) => x > 1 && x < 199); console.log(`Cursor motion: ${intermediate.length} intermediate frames; cursor retained=${sample.samePointer}; participant retained=${sample.sameParticipant}; unrelated DOM mutations=${sample.unrelatedMutations}`); if (reducedMotion) expect(intermediate.length).toBe(0); else expect(intermediate.length, "The remote cursor should move on frames between network packets").toBeGreaterThanOrEqual(2); @@ -77,33 +78,34 @@ test("remote dragging interpolates the shape and its selection together while lo const shape = page.getByRole("button", { name: "Sticky note: Moving note", exact: true }); await shape.scrollIntoViewIfNeeded(); const box = await shape.boundingBox(); + if (!box) throw new Error("Expected a visible element"); await page.mouse.move(box.x + 30, box.y + 30); await page.mouse.down(); await peer.evaluate(() => { - const shape = document.querySelector(".whiteboard-object"); - const ring = document.querySelector(".remote-selection"); + const shape = document.querySelector(".whiteboard-object")!; + const ring = document.querySelector(".remote-selection")!; const origin = shape.getBoundingClientRect().x; - const frames = []; + const frames: { x: number; separation: number; }[] = []; window.dragMotionSample = new Promise(resolve => { const started = performance.now(); - function frame(time) { + function frame(time: number) { frames.push({ x: shape.getBoundingClientRect().x - origin, separation: Math.abs(shape.getBoundingClientRect().x - ring.getBoundingClientRect().x) }); if (time - started < 500) requestAnimationFrame(frame); - else resolve({ frames, sameRing: document.querySelector(".remote-selection") === ring }); + else resolve({ frames, sameRing: document.querySelector(".remote-selection")! === ring }); } requestAnimationFrame(frame); }); }); await page.mouse.move(box.x + 150, box.y + 30); - const localX = await shape.evaluate(node => new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(() => resolve(node.getBoundingClientRect().x))))); + const localX = await shape.evaluate(node => new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(() => resolve(node.getBoundingClientRect().x))))); expect(localX - box.x).toBeCloseTo(120, 0); const sample = await peer.evaluate(() => window.dragMotionSample); - const intermediate = sample.frames.filter(frame => frame.x > 1 && frame.x < 119); - console.log(`Drag motion: ${intermediate.length} intermediate frames; max selection separation=${Math.max(...sample.frames.map(frame => frame.separation)).toFixed(2)}px`); + const intermediate = sample.frames.filter((frame: { x: number; }) => frame.x > 1 && frame.x < 119); + console.log(`Drag motion: ${intermediate.length} intermediate frames; max selection separation=${Math.max(...sample.frames.map((frame: { separation: any; }) => frame.separation)).toFixed(2)}px`); expect(intermediate.length).toBeGreaterThanOrEqual(2); - expect(sample.frames.at(-1).x).toBeCloseTo(120, 0); - expect(Math.max(...sample.frames.map(frame => frame.separation))).toBeLessThan(1); + expect(sample.frames.at(-1)!.x).toBeCloseTo(120, 0); + expect(Math.max(...sample.frames.map((frame: { separation: any; }) => frame.separation))).toBeLessThan(1); expect(sample.sameRing).toBe(true); await page.mouse.up(); await expect(page.locator("#save-status")).toHaveText("Saved"); diff --git a/examples/collaboration/tests/whiteboard.spec.js b/examples/collaboration/tests/whiteboard.spec.ts similarity index 89% rename from examples/collaboration/tests/whiteboard.spec.js rename to examples/collaboration/tests/whiteboard.spec.ts index 61dbb1a..f902090 100644 --- a/examples/collaboration/tests/whiteboard.spec.js +++ b/examples/collaboration/tests/whiteboard.spec.ts @@ -1,20 +1,21 @@ import { randomUUID } from "node:crypto"; -import { test, expect } from "./fixtures.js"; -import { api, register } from "./access-helpers.js"; +import { test, expect } from "./fixtures.ts"; +import { api, register } from "./access-helpers.ts"; +import { Locator, Page } from "@playwright/test"; -async function setup(page, baseURL) { +async function setup(page: Page, baseURL: string|undefined) { const user = await register(page.request, baseURL); const roomId = `whiteboard-${randomUUID()}`; expect((await api(page.request, baseURL, "/api/rooms", "POST", { room_id: roomId })).ok()).toBe(true); return { user, roomId, url: `${baseURL}/whiteboard.html?room=${roomId}` }; } -const saved = page => expect(page.locator("#save-status")).toHaveText("Saved"); -const note = (page, text) => page.getByRole("button", { name: `Sticky note: ${text}`, exact: true }); -const position = locator => locator.evaluate(node => { +const saved = (page: Page) => expect(page.locator("#save-status")).toHaveText("Saved"); +const note = (page: Page, text: string) => page.getByRole("button", { name: `Sticky note: ${text}`, exact: true }); +const position = (locator: Locator) => locator.evaluate((node: Element) => { const transform = new DOMMatrixReadOnly(getComputedStyle(node).transform); return { x: transform.m41, y: transform.m42 }; }); -async function addNote(page, text) { +async function addNote(page: Page, text: string) { await page.getByRole("button", { name: "+ Sticky note", exact: true }).click(); await page.getByLabel("Object text").fill(text); await saved(page); @@ -36,6 +37,7 @@ test("whiteboard syncs objects, live drag previews, cursors, selections, and loc await note(page, "Launch ideas").scrollIntoViewIfNeeded(); const before = await position(note(page, "Launch ideas")); const box = await note(page, "Launch ideas").boundingBox(); + if (!box) throw new Error("Expected a visible element"); await page.mouse.move(box.x + 50, box.y + 50); await page.mouse.down(); await page.mouse.move(box.x + 140, box.y + 100, { steps: 8 }); @@ -59,6 +61,7 @@ test("whiteboard cancels a drag, supports keyboard movement at zoom, resizing an await note(page, "Keep this idea").scrollIntoViewIfNeeded(); const before = await position(note(page, "Keep this idea")); const box = await note(page, "Keep this idea").boundingBox(); + if (!box) throw new Error("Expected a visible element"); await page.mouse.move(box.x + 30, box.y + 30); await page.mouse.down(); await page.mouse.move(box.x + 90, box.y + 75, { steps: 5 }); @@ -122,7 +125,7 @@ test("whiteboard viewers cannot mutate objects; revocation and mobile navigation try { const viewer = await viewerContext.newPage(); const user = await register(viewer.request, baseURL); - const role = async value => expect((await api(page.request, baseURL, `/api/rooms/${roomId}/members/${user.username}`, "PUT", { role: value })).ok()).toBe(true); + const role = async (value: string) => expect((await api(page.request, baseURL, `/api/rooms/${roomId}/members/${user.username}`, "PUT", { role: value })).ok()).toBe(true); await role("viewer"); await viewer.goto(url); await expect(viewer.locator("#save-status")).toHaveText("View only"); @@ -147,5 +150,5 @@ test("whiteboard viewers cannot mutate objects; revocation and mobile navigation await expect(viewer.getByLabel("Object text")).not.toBeEditable(); await expect(viewer.getByRole("button", { name: "Delete object", exact: true })).toBeDisabled(); } finally { await viewerContext.close(); } - await expect(page.getByRole("link", { name: "Try the Kanban board", exact: true })).toHaveAttribute("href", `${baseURL}/kanban.html?room=${roomId}`); + await expect(page.getByRole("link", { name: "Try the Kanban board", exact: true })).toHaveJSProperty("href", `${baseURL}/kanban.html?room=${roomId}`); }); diff --git a/examples/collaboration/tsconfig.json b/examples/collaboration/tsconfig.json new file mode 100644 index 0000000..00cb964 --- /dev/null +++ b/examples/collaboration/tsconfig.json @@ -0,0 +1,44 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": [ + "dom", + "dom.iterable", + "es2024" + ], + "strict": true, + "noEmit": true, + "skipLibCheck": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "react-jsx", + "allowImportingTsExtensions": true, + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": [ + "./*" + ] + }, + "allowJs": false + }, + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts", + ".next/dev/types/**/*.ts" + ], + "exclude": [ + "node_modules", + "out", + "dist" + ] +} diff --git a/examples/collaboration/vite.config.js b/examples/collaboration/vite.config.js deleted file mode 100644 index 4362861..0000000 --- a/examples/collaboration/vite.config.js +++ /dev/null @@ -1,33 +0,0 @@ -import { defineConfig } from "vite"; -import { fileURLToPath } from "node:url"; - -export default defineConfig({ - build: { - rolldownOptions: { - input: { - collaboration: fileURLToPath(new URL("./index.html", import.meta.url)), - settings: fileURLToPath(new URL("./sdk.html", import.meta.url)), - kanban: fileURLToPath(new URL("./kanban.html", import.meta.url)), - table: fileURLToPath(new URL("./table.html", import.meta.url)), - flowchart: fileURLToPath(new URL("./flowchart.html", import.meta.url)), - whiteboard: fileURLToPath(new URL("./whiteboard.html", import.meta.url)), - richText: fileURLToPath(new URL("./rich-text.html", import.meta.url)), - multiplayerForm: fileURLToPath(new URL("./multiplayer-form.html", import.meta.url)), - }, - }, - }, - server: { - host: "127.0.0.1", - port: 5173, - strictPort: true, - proxy: { - "/api": { - target: process.env.SYNIXIR_ENDPOINT ?? "http://127.0.0.1:4000", - }, - "/socket": { - target: process.env.SYNIXIR_ENDPOINT ?? "http://127.0.0.1:4000", - ws: true, - }, - }, - }, -}); diff --git a/examples/collaboration/whiteboard.html b/examples/collaboration/whiteboard.html deleted file mode 100644 index 0194500..0000000 --- a/examples/collaboration/whiteboard.html +++ /dev/null @@ -1,101 +0,0 @@ - - - - - - Synixir · Collaborative Whiteboard - - - - -
    -
    - Synixir - -
    - - - - - - -
    - - - diff --git a/examples/collaboration/whiteboard.js b/examples/collaboration/whiteboard.js deleted file mode 100644 index f7004bc..0000000 --- a/examples/collaboration/whiteboard.js +++ /dev/null @@ -1,5 +0,0 @@ -import { startExample } from "./example-shell.js"; -import { createWhiteboard } from "./whiteboard/canvas.js"; -import "./whiteboard/canvas.css"; - -startExample({ createSurface: createWhiteboard, pagePath: "/whiteboard.html", presenceOptions: { avatars: true } }); diff --git a/examples/collaboration/whiteboard/canvas.js b/examples/collaboration/whiteboard/canvas.ts similarity index 75% rename from examples/collaboration/whiteboard/canvas.js rename to examples/collaboration/whiteboard/canvas.ts index 05243ff..7dea342 100644 --- a/examples/collaboration/whiteboard/canvas.js +++ b/examples/collaboration/whiteboard/canvas.ts @@ -1,28 +1,29 @@ -import { createWhiteboardModel, canvasSize, colors } from "./model.js"; -import { createMotion } from "./motion.js"; +import type { SynixirRoom } from "@synixir/client"; +import { createWhiteboardModel, canvasSize, colors } from "./model.ts"; +import { createMotion } from "./motion.ts"; -export function createWhiteboard(room) { - const root = document.querySelector("#editor"); - document.querySelector("#open-peer").href = window.location.href; +export function createWhiteboard(room: SynixirRoom) { + const root = document.querySelector("#editor")!; + document.querySelector("#open-peer")!.href = window.location.href; const model = createWhiteboardModel(room.doc); const motion = createMotion(); let items = model.list(); - let activityFrame; + let activityFrame: number|undefined; const events = new AbortController(); - const on = (target, event, handler) => target.addEventListener(event, handler, { signal: events.signal }); + const on = (target: EventTarget, event: K, handler: (event: DocumentEventMap[K]) => void, capture = false) => target.addEventListener(event, handler as EventListener, { signal: events.signal, capture }); const nodes = new Map(); const peerNodes = new Map(); let readOnly = true; - let selectedId = null; - let drag = null; - let cursor = null; + let selectedId: string|null = null; + let drag: { moved: any; id: any; position: any; pointerId: any; start: any; original: any; size: any; }|null = null; + let cursor: { x: number; y: number; }|null = null; let zoom = 1; - let presenceTimer; + let presenceTimer: string|number|NodeJS.Timeout|undefined; let lastPresenceAt = -Infinity; let disposed = false; - const undo = document.querySelector("#undo"); - const redo = document.querySelector("#redo"); - const announce = text => { document.querySelector("#board-announcement").textContent = text; }; + const undo = document.querySelector("#undo")!; + const redo = document.querySelector("#redo")!; + const announce = (text: string|null) => { document.querySelector("#board-announcement")!.textContent = text; }; root.innerHTML = `
    @@ -43,15 +44,15 @@ export function createWhiteboard(room) {
    `; - const viewport = root.querySelector("#whiteboard-viewport"); - const sizer = root.querySelector(".canvas-sizer"); - const canvas = root.querySelector(".whiteboard-canvas"); - const layer = root.querySelector(".object-layer"); - const presenceLayer = root.querySelector(".presence-layer"); - const textInput = root.querySelector("#object-text"); - const colorInput = root.querySelector("#object-color"); - const widthInput = root.querySelector("#object-width"); - const heightInput = root.querySelector("#object-height"); + const viewport = root.querySelector("#whiteboard-viewport")!; + const sizer = root.querySelector(".canvas-sizer")!; + const canvas = root.querySelector(".whiteboard-canvas")!; + const layer = root.querySelector(".object-layer")!; + const presenceLayer = root.querySelector(".presence-layer")!; + const textInput = root.querySelector("#object-text")!; + const colorInput = root.querySelector("#object-color")!; + const widthInput = root.querySelector("#object-width")!; + const heightInput = root.querySelector("#object-height")!; canvas.style.width = `${canvasSize.width}px`; canvas.style.height = `${canvasSize.height}px`; for (const color of colors) { @@ -61,9 +62,9 @@ export function createWhiteboard(room) { colorInput.append(option); } const selected = () => items.find(item => item.id === selectedId); - const validPoint = point => point && Number.isFinite(point.x) && Number.isFinite(point.y) && + const validPoint = (point: { x: any; y: any; }) => point && Number.isFinite(point.x) && Number.isFinite(point.y) && point.x >= 0 && point.y >= 0 && point.x <= canvasSize.width && point.y <= canvasSize.height; - const worldPoint = event => { + const worldPoint = (event: { clientX: number; clientY: number; }) => { const rect = canvas.getBoundingClientRect(); return { x: (event.clientX - rect.left) / zoom, y: (event.clientY - rect.top) / zoom }; }; @@ -81,8 +82,8 @@ export function createWhiteboard(room) { if (remaining <= 0) publish(); else presenceTimer = setTimeout(publish, remaining); } - function select(id) { - selectedId = id; + function select(id: string|null|undefined) { + selectedId = id ?? null; model.history.stopCapturing(); render(); publish(); @@ -94,7 +95,7 @@ export function createWhiteboard(room) { publish(); scheduleActivity(); } - function setZoom(value) { + function setZoom(value: number) { cancelDrag(); const center = { x: (viewport.scrollLeft + viewport.clientWidth / 2) / zoom, y: (viewport.scrollTop + viewport.clientHeight / 2) / zoom }; @@ -104,16 +105,16 @@ export function createWhiteboard(room) { sizer.style.height = `${canvasSize.height * zoom}px`; viewport.scrollLeft = center.x * zoom - viewport.clientWidth / 2; viewport.scrollTop = center.y * zoom - viewport.clientHeight / 2; - root.querySelector("#zoom-level").textContent = `${Math.round(zoom * 100)}%`; - root.querySelector("#zoom-out").disabled = zoom <= 0.5; - root.querySelector("#zoom-in").disabled = zoom >= 1.5; + root.querySelector("#zoom-level")!.textContent = `${Math.round(zoom * 100)}%`; + root.querySelector("#zoom-out")!.disabled = zoom <= 0.5; + root.querySelector("#zoom-in")!.disabled = zoom >= 1.5; cursor = null; publish(); } - on(root.querySelector("#zoom-in"), "click", () => setZoom(zoom + 0.25)); - on(root.querySelector("#zoom-out"), "click", () => setZoom(zoom - 0.25)); - on(root.querySelector("#zoom-reset"), "click", () => setZoom(1)); - root.querySelectorAll("[data-add]").forEach(button => on(button, "click", () => { + on(root.querySelector("#zoom-in")!, "click", () => setZoom(zoom + 0.25)); + on(root.querySelector("#zoom-out")!, "click", () => setZoom(zoom - 0.25)); + on(root.querySelector("#zoom-reset")!, "click", () => setZoom(1)); + root.querySelectorAll("[data-add]").forEach(button => on(button, "click", () => { if (readOnly) return; cancelDrag(); const offset = model.list().length % 5 * 24; @@ -124,9 +125,9 @@ export function createWhiteboard(room) { textInput.select(); announce("Object added."); })); - on(viewport, "pointerdown", event => { + on(viewport, "pointerdown", (event) => { if (event.button !== 0 || !event.isPrimary) return; - const node = event.target.closest(".whiteboard-object"); + const node = (event.target as Element).closest(".whiteboard-object"); if (!node) { select(null); return; } event.preventDefault(); node.focus({ preventScroll: true }); @@ -138,7 +139,7 @@ export function createWhiteboard(room) { position: item.position, size: item.size, moved: false }; viewport.setPointerCapture(event.pointerId); }); - on(viewport, "pointermove", event => { + on(viewport, "pointermove", (event) => { const point = worldPoint(event); cursor = validPoint(point) ? point : null; if (drag && event.pointerId === drag.pointerId && !readOnly) { @@ -149,7 +150,7 @@ export function createWhiteboard(room) { } schedulePresence(); }); - on(viewport, "pointerup", event => { + on(viewport, "pointerup", (event) => { if (!drag || event.pointerId !== drag.pointerId) return; const completed = drag; drag = null; @@ -167,11 +168,11 @@ export function createWhiteboard(room) { on(viewport, "scroll", () => { cursor = null; schedulePresence(); }); on(window, "blur", () => { cursor = null; cancelDrag(); }); on(document, "visibilitychange", () => { if (document.hidden) { cursor = null; cancelDrag(); } }); - on(viewport, "click", event => { - const node = event.target.closest(".whiteboard-object"); + on(viewport, "click", (event) => { + const node = (event.target as Element).closest(".whiteboard-object"); if (node) select(node.dataset.objectId); }); - on(root, "keydown", event => { + on(root, "keydown", (event) => { if (event.key === "Escape") { if (drag) cancelDrag(); else select(null); @@ -185,7 +186,7 @@ export function createWhiteboard(room) { if (event.shiftKey) model.history.redo(); else model.history.undo(); return; } - if (event.target !== viewport && !event.target.closest(".whiteboard-object")) return; + if (event.target !== viewport && !(event.target as Element).closest(".whiteboard-object")) return; const item = selected(); if (!item) return; if (["Delete", "Backspace"].includes(event.key)) { event.preventDefault(); model.remove(item.id); return; } @@ -196,7 +197,7 @@ export function createWhiteboard(room) { model.move(item.id, { x: item.position.x + delta[0] * step, y: item.position.y + delta[1] * step }); } }); - for (const [input, field] of [[textInput, "text"], [colorInput, "color"]]) { + for (const [input, field] of [[textInput, "text"], [colorInput, "color"]] as const) { on(input, "focus", () => model.history.stopCapturing()); on(input, "blur", () => model.history.stopCapturing()); on(input, "input", () => { if (!readOnly && selectedId) model.edit(selectedId, field, input.value); }); @@ -205,8 +206,8 @@ export function createWhiteboard(room) { if (!readOnly && selectedId) model.resize(selectedId, { width: widthInput.valueAsNumber, height: heightInput.valueAsNumber }); renderInspector(); }); - on(root.querySelector("#bring-front"), "click", () => { if (!readOnly && selectedId) model.front(selectedId); }); - on(root.querySelector("#delete-object"), "click", () => { if (!readOnly && selectedId) model.remove(selectedId); }); + on(root.querySelector("#bring-front")!, "click", () => { if (!readOnly && selectedId) model.front(selectedId); }); + on(root.querySelector("#delete-object")!, "click", () => { if (!readOnly && selectedId) model.remove(selectedId); }); on(undo, "click", () => { if (!readOnly) { cancelDrag(); model.history.undo(); } }); on(redo, "click", () => { if (!readOnly) { cancelDrag(); model.history.redo(); } }); @@ -243,9 +244,9 @@ export function createWhiteboard(room) { if (node.textContent !== item.text) node.textContent = item.text; Object.assign(node.style, { width: `${item.size.width}px`, height: `${item.size.height}px`, zIndex: String(index) }); }); - root.querySelector(".canvas-empty").hidden = items.length > 0; + root.querySelector(".canvas-empty")!.hidden = items.length > 0; const count = `${items.length} ${items.length === 1 ? "object" : "objects"}`; - if (root.querySelector("#object-count").textContent !== count) root.querySelector("#object-count").textContent = count; + if (root.querySelector("#object-count")!.textContent !== count) root.querySelector("#object-count")!.textContent = count; } function renderActivity() { activityFrame = undefined; @@ -256,8 +257,8 @@ export function createWhiteboard(room) { const node = nodes.get(item.id); if (!node) continue; const ownDrag = drag?.id === item.id; - const remoteDrag = remote.findLast(peer => peer.drag?.id === item.id && validPoint(peer.drag))?.drag; - const preview = ownDrag ? drag.position : remoteDrag; + const remoteDrag = remote.findLast((peer: { drag: { id: string; x: number; y: number }; }) => peer.drag?.id === item.id && validPoint(peer.drag))?.drag; + const preview = ownDrag && drag ? drag.position : remoteDrag; const position = preview ? model.boundedPosition(preview, item.size) : item.position; const smooth = !ownDrag && (Boolean(remoteDrag) || motion.moving(node)); motion.move(node, position, smooth); @@ -318,10 +319,10 @@ export function createWhiteboard(room) { function renderInspector() { const item = selected(); - root.querySelector("#selection-empty").hidden = Boolean(item); - root.querySelector("#selection-fields").hidden = !item; + root.querySelector("#selection-empty")!.hidden = Boolean(item); + root.querySelector("#selection-fields")!.hidden = !item; if (!item) return; - root.querySelector("#selection-title").textContent = item.kind === "sticky" ? "Sticky note" : item.kind === "ellipse" ? "Ellipse" : "Rectangle"; + root.querySelector("#selection-title")!.textContent = item.kind === "sticky" ? "Sticky note" : item.kind === "ellipse" ? "Ellipse" : "Rectangle"; if (textInput.value !== item.text) { const start = textInput.selectionStart; const end = textInput.selectionEnd; @@ -329,10 +330,10 @@ export function createWhiteboard(room) { if (document.activeElement === textInput) textInput.setSelectionRange(Math.min(start, item.text.length), Math.min(end, item.text.length)); } colorInput.value = item.color; - widthInput.value = item.size.width; - heightInput.value = item.size.height; + widthInput.value = String(item.size.width); + heightInput.value = String(item.size.height); textInput.readOnly = readOnly; - for (const input of [colorInput, widthInput, heightInput, root.querySelector("#bring-front"), root.querySelector("#delete-object")]) input.disabled = readOnly; + for (const input of [colorInput, widthInput, heightInput, root.querySelector("#bring-front")!, root.querySelector("#delete-object")!]) input.disabled = readOnly; } function renderHistory() { undo.disabled = readOnly || !model.history.canUndo(); @@ -355,13 +356,13 @@ export function createWhiteboard(room) { } model.objects.observeDeep(render); room.awareness.on("change", scheduleActivity); - for (const event of ["stack-item-added", "stack-item-popped", "stack-cleared", "stack-item-updated"]) model.history.on(event, renderHistory); + for (const event of ["stack-item-added", "stack-item-popped", "stack-cleared", "stack-item-updated"] as const) model.history.on(event, renderHistory); setZoom(1); render(); function destroy() { disposed = true; clearTimeout(presenceTimer); - cancelAnimationFrame(activityFrame); + if (activityFrame !== undefined) cancelAnimationFrame(activityFrame); motion.destroy(); peerNodes.clear(); events.abort(); @@ -371,11 +372,11 @@ export function createWhiteboard(room) { nodes.clear(); root.replaceChildren(); } - destroy.setReadOnly = value => { + destroy.setReadOnly = (value: boolean) => { if (readOnly !== value) { readOnly = value; if (readOnly) cancelDrag(); - root.querySelectorAll("[data-add]").forEach(button => { button.disabled = readOnly; }); + root.querySelectorAll("[data-add]").forEach(button => { button.disabled = readOnly; }); render(); } scheduleActivity(); diff --git a/examples/collaboration/whiteboard/model.test.js b/examples/collaboration/whiteboard/model.test.ts similarity index 96% rename from examples/collaboration/whiteboard/model.test.js rename to examples/collaboration/whiteboard/model.test.ts index f120b41..1a82a4f 100644 --- a/examples/collaboration/whiteboard/model.test.js +++ b/examples/collaboration/whiteboard/model.test.ts @@ -1,7 +1,7 @@ import { test } from "node:test"; import assert from "node:assert/strict"; import * as Y from "yjs"; -import { createWhiteboardModel, canvasSize } from "./model.js"; +import { createWhiteboardModel, canvasSize } from "./model.ts"; function clients() { const a = new Y.Doc(); @@ -48,6 +48,7 @@ test("objects stay within the canvas and concurrent creation preserves both obje first.resize(id, { width: 500, height: 500 }); sync(); const item = first.list().find(item => item.id === id); + assert.ok(item); assert.ok(item.position.x + item.size.width <= canvasSize.width); assert.ok(item.position.y + item.size.height <= canvasSize.height); } finally { close(); } diff --git a/examples/collaboration/whiteboard/model.js b/examples/collaboration/whiteboard/model.ts similarity index 65% rename from examples/collaboration/whiteboard/model.js rename to examples/collaboration/whiteboard/model.ts index 1d0afcf..c7e06fb 100644 --- a/examples/collaboration/whiteboard/model.js +++ b/examples/collaboration/whiteboard/model.ts @@ -1,30 +1,25 @@ import * as Y from "yjs"; +import { canvasItem, type Point, type Size } from "../lib/canvas-data.ts"; export const canvasSize = { width: 1600, height: 1000 }; export const kinds = ["sticky", "rectangle", "ellipse"]; export const colors = ["yellow", "blue", "mint", "rose", "white"]; -const clamp = (value, min, max) => Math.min(max, Math.max(min, value)); +const clamp = (value: number, min: number, max: number) => Math.min(max, Math.max(min, value)); -export function createWhiteboardModel(doc) { +export function createWhiteboardModel(doc: Y.Doc) { const objects = doc.getMap("whiteboard:objects:v1"); const origin = {}; const history = new Y.UndoManager(objects, { trackedOrigins: new Set([origin]) }); function list() { - return [...objects.entries()].filter(([, item]) => item instanceof Y.Map).map(([id, item]) => ({ - id, kind: item.get("kind"), text: String(item.get("text") ?? ""), - color: colors.includes(item.get("color")) ? item.get("color") : "yellow", - position: item.get("position"), size: item.get("size"), order: item.get("order"), - })).filter(item => kinds.includes(item.kind) && Number.isFinite(item.order) && - Number.isFinite(item.position?.x) && Number.isFinite(item.position?.y) && - Number.isFinite(item.size?.width) && Number.isFinite(item.size?.height) && - item.size.width >= 80 && item.size.width <= 600 && item.size.height >= 60 && item.size.height <= 500) + return [...objects.entries()].map(([id, value]) => canvasItem(id, value, kinds, colors)) + .filter(item => item !== undefined) .map(item => ({ ...item, position: boundedPosition(item.position, item.size) })) .sort((a, b) => a.order - b.order || a.id.localeCompare(b.id)); } - function boundedPosition(position, size) { + function boundedPosition(position: Point, size: Size) { return { x: clamp(position.x, 0, canvasSize.width - size.width), y: clamp(position.y, 0, canvasSize.height - size.height) }; } - function action(fn) { + function action(fn: () => void) { history.stopCapturing(); doc.transact(fn, origin); history.stopCapturing(); @@ -32,8 +27,8 @@ export function createWhiteboardModel(doc) { const nextOrder = () => list().reduce((max, item) => Math.max(max, item.order), 0) + 1; return { objects, history, list, boundedPosition, - add(kind, position = { x: 80, y: 80 }) { - if (!kinds.includes(kind) || !Number.isFinite(position.x) || !Number.isFinite(position.y)) return; + add(kind: string|undefined, position = { x: 80, y: 80 }) { + if (!kind || !kinds.includes(kind) || !Number.isFinite(position.x) || !Number.isFinite(position.y)) return; const id = crypto.randomUUID(); const size = kind === "sticky" ? { width: 220, height: 180 } : { width: 240, height: 140 }; action(() => objects.set(id, new Y.Map([ @@ -43,33 +38,34 @@ export function createWhiteboardModel(doc) { ]))); return id; }, - move(id, position) { - const item = objects.get(id); + move(id: string|undefined, position: Point) { + const item = objects.get(id ?? ""); if (!(item instanceof Y.Map) || !Number.isFinite(position.x) || !Number.isFinite(position.y)) return; const next = boundedPosition(position, item.get("size")); const previous = item.get("position"); if (previous.x === next.x && previous.y === next.y) return; action(() => item.set("position", next)); }, - resize(id, size) { - const item = objects.get(id); + resize(id: string|undefined, size: Size) { + const item = objects.get(id ?? ""); if (!(item instanceof Y.Map) || !Number.isFinite(size.width) || !Number.isFinite(size.height)) return; const position = item.get("position"); const next = { width: clamp(size.width, 80, Math.min(600, canvasSize.width - position.x)), height: clamp(size.height, 60, Math.min(500, canvasSize.height - position.y)) }; action(() => item.set("size", next)); }, - edit(id, field, value) { - const item = objects.get(id); + edit(id: string|undefined, field: string, value: string) { + const item = objects.get(id ?? ""); if (!(item instanceof Y.Map) || !["text", "color"].includes(field) || typeof value !== "string") return; if (field === "color" && !colors.includes(value)) return; if (item.get(field) === value) return; doc.transact(() => item.set(field, field === "text" ? value.slice(0, 2000) : value), origin); }, - front(id) { - if (objects.get(id) instanceof Y.Map) action(() => objects.get(id).set("order", nextOrder())); + front(id: string) { + const item = objects.get(id); + if (item instanceof Y.Map) action(() => item.set("order", nextOrder())); }, - remove(id) { action(() => objects.delete(id)); }, + remove(id: string|undefined) { action(() => objects.delete(id ?? "")); }, destroy() { history.destroy(); }, }; } diff --git a/examples/collaboration/whiteboard/motion.js b/examples/collaboration/whiteboard/motion.ts similarity index 71% rename from examples/collaboration/whiteboard/motion.js rename to examples/collaboration/whiteboard/motion.ts index 471cc73..2c681e0 100644 --- a/examples/collaboration/whiteboard/motion.js +++ b/examples/collaboration/whiteboard/motion.ts @@ -1,16 +1,18 @@ // Interpolate received positions over a little more than the 60 ms presence // interval. New targets start from the position currently on screen, so packet // timing never restarts a transition from an old network position. -export function createMotion({ onPaint = () => {} } = {}) { - const positions = new Map(); +import type { Point } from "../lib/canvas-data.ts"; +interface MotionState { current: Point; target: Point; from: Point; moving: boolean; started: number } +export function createMotion({ onPaint = () => {} }: { onPaint?: (node: HTMLElement, point: Point) => void } = {}) { + const positions = new Map(); const reducedMotion = matchMedia("(prefers-reduced-motion: reduce)"); const duration = 80; - let frame; - const paint = (node, point) => { + let frame: number|undefined; + const paint = (node: HTMLElement, point: Point) => { node.style.transform = `translate3d(${point.x}px, ${point.y}px, 0)`; onPaint(node, point); }; - function tick(time) { + function tick(time: number) { frame = undefined; let pending = false; for (const [node, state] of positions) { @@ -25,7 +27,7 @@ export function createMotion({ onPaint = () => {} } = {}) { if (pending) frame = requestAnimationFrame(tick); } function settle() { - cancelAnimationFrame(frame); + if (frame !== undefined) cancelAnimationFrame(frame); frame = undefined; for (const [node, state] of positions) { if (!state.moving) continue; @@ -38,11 +40,11 @@ export function createMotion({ onPaint = () => {} } = {}) { reducedMotion.addEventListener("change", settle); document.addEventListener("visibilitychange", onVisibility); return { - move(node, target, smooth = true) { + move(node: HTMLElement, target: Point, smooth = true) { let state = positions.get(node); if (state && !state.moving && state.current.x === target.x && state.current.y === target.y) return; if (!state || !smooth || reducedMotion.matches || document.hidden) { - state = { current: target, target, moving: false }; + state = { current: target, target, from: target, moving: false, started: 0 }; positions.set(node, state); paint(node, target); return; @@ -54,10 +56,10 @@ export function createMotion({ onPaint = () => {} } = {}) { state.moving = true; if (frame === undefined) frame = requestAnimationFrame(tick); }, - moving(node) { return Boolean(positions.get(node)?.moving); }, - forget(node) { positions.delete(node); }, + moving(node: HTMLElement) { return Boolean(positions.get(node)?.moving); }, + forget(node: HTMLElement) { positions.delete(node); }, destroy() { - cancelAnimationFrame(frame); + if (frame !== undefined) cancelAnimationFrame(frame); positions.clear(); reducedMotion.removeEventListener("change", settle); document.removeEventListener("visibilitychange", onVisibility); diff --git a/lib/synixir_web.ex b/lib/synixir_web.ex index 38595b1..50a24cb 100644 --- a/lib/synixir_web.ex +++ b/lib/synixir_web.ex @@ -17,7 +17,7 @@ defmodule SynixirWeb do those modules here. """ - def static_paths, do: ~w(assets fonts images favicon.ico robots.txt) + def static_paths, do: ~w(_next assets fonts images favicon.ico robots.txt) def router do quote do diff --git a/lib/synixir_web/controllers/client_controller.ex b/lib/synixir_web/controllers/client_controller.ex index 62fa0fb..18cdbac 100644 --- a/lib/synixir_web/controllers/client_controller.ex +++ b/lib/synixir_web/controllers/client_controller.ex @@ -16,6 +16,7 @@ defmodule SynixirWeb.ClientController do if File.regular?(path), do: conn |> put_resp_content_type("text/html") |> send_file(200, path), - else: send_resp(conn, 404, "Client assets are not built. Use the Vite development server.") + else: + send_resp(conn, 404, "Client assets are not built. Use the Next.js development server.") end end diff --git a/package-lock.json b/package-lock.json index e289061..72d1813 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,13 @@ "examples/collaboration" ], "devDependencies": { - "typescript": "7.0.2" + "@types/http-proxy": "1.17.17", + "@types/node": "22.20.2", + "@types/phoenix": "1.6.7", + "http-proxy": "1.18.1", + "tsx": "4.23.13", + "typescript": "7.0.2", + "vite": "8.2.2" }, "engines": { "node": ">=22.12.0" @@ -23,1826 +29,9321 @@ "@codemirror/state": "6.7.4", "@codemirror/view": "6.43.11", "@synixir/client": "0.1.0", + "@tailwindcss/postcss": "4.3.3", "@tiptap/core": "3.31.3", "@tiptap/extension-collaboration": "3.31.3", "@tiptap/pm": "3.31.3", "@tiptap/starter-kit": "3.31.3", "@tiptap/y-tiptap": "3.0.9", + "class-variance-authority": "0.7.1", + "cn": "0.3.0", + "lucide-react": "1.45.0", + "next": "16.3.5", + "radix-ui": "1.6.7", + "react": "19.3.0", + "react-dom": "19.3.0", + "shadcn": "4.21.0", + "tailwindcss": "4.3.3", + "tw-animate-css": "1.4.0", "y-codemirror.next": "0.3.6", "yjs": "13.6.32" }, "devDependencies": { "@playwright/test": "1.63.0", - "vite": "8.2.2" + "@types/react": "19.2.18", + "@types/react-dom": "19.2.7" }, "engines": { "node": ">=22.12.0" } }, - "node_modules/@codemirror/commands": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.11.0.tgz", - "integrity": "sha512-/K4Rl5BN0OtTiPWmJCdqODu38XnDMsDxKY5rgrPnCkutPTJf2wVbkoixLfealF5Kwse/s8P8M5jAiURiwSwnFA==", + "node_modules/@alloc/quick-lru": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.3.0.tgz", + "integrity": "sha512-U4+70Pc5ZS9osnCBCE5Jha/ciHM+Yp+CNMNC/7HvYbNRk1Ldd+f7qO65W5qfhu/TCv+/ozljlXXe9Nj8419DMA==", "license": "MIT", - "dependencies": { - "@codemirror/language": "^6.0.0", - "@codemirror/state": "^6.7.0", - "@codemirror/view": "^6.27.0", - "@lezer/common": "^1.1.0" + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@codemirror/language": { - "version": "6.12.4", - "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.12.4.tgz", - "integrity": "sha512-1q4PaT+o6PbgpkJt4Q8Fv5XJxTy4FUZ4MWETtyiDw3J0Pyr9E2vqcKL+k9wcvjNTIsauxvE7OfmWj3FRPHQ76A==", + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", "license": "MIT", "dependencies": { - "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.23.0", - "@lezer/common": "^1.5.0", - "@lezer/highlight": "^1.0.0", - "@lezer/lr": "^1.0.0", - "style-mod": "^4.0.0" + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@codemirror/state": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.7.4.tgz", - "integrity": "sha512-QhQIVRY+xHZDxwOSFrJ1eUMapJBUID3IdeAjf7dHO7zBUzSkyooHiodnalz5MG3iHzwixKMlAAyn7244y537EA==", + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", "license": "MIT", - "peer": true, - "dependencies": { - "@marijn/find-cluster-break": "^1.0.0" + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@codemirror/view": { - "version": "6.43.11", - "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.43.11.tgz", - "integrity": "sha512-2+esucbQX6wB2JYi1eDvdCPFTA31BN8oSy6xCmk3G6CloV11yOvEjYk+gH7kLrP0MuHG94E8WDhjs5oMiu3+Wg==", + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", "license": "MIT", "peer": true, "dependencies": { - "@codemirror/state": "^6.7.0", - "crelt": "^1.0.6", - "style-mod": "^4.1.0", - "w3c-keyname": "^2.2.4" + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" } }, - "node_modules/@lezer/common": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.5.2.tgz", - "integrity": "sha512-sxQE460fPZyU3sdc8lafxiPwJHBzZRy/udNFynGQky1SePYBdhkBl1kOagA9uT3pxR8K09bOrmTUqA9wb/PjSQ==", - "license": "MIT" + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } }, - "node_modules/@lezer/highlight": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.3.tgz", - "integrity": "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==", + "node_modules/@babel/generator": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz", + "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", "license": "MIT", "dependencies": { - "@lezer/common": "^1.3.0" + "@babel/parser": "^7.29.8", + "@babel/types": "^7.29.8", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@lezer/lr": { - "version": "1.4.10", - "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.10.tgz", - "integrity": "sha512-rnCpTIBafOx4mRp43xOxDJbFipJm/c0cia/V5TiGlhmMa+wsSdoGmUN3w5Bqrks/09Q/D4tNAmWaT8p6NRi77A==", + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.29.7.tgz", + "integrity": "sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==", "license": "MIT", "dependencies": { - "@lezer/common": "^1.0.0" + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@marijn/find-cluster-break": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.4.tgz", - "integrity": "sha512-Wy0V7+SGUjnF9/TkiM1hKVDPj7jKXduPNboMVtHTA8dySMURWqfg/JZ9E2Sq8JgSJmkl7k7Qe9FLeMSrSraWmQ==", - "license": "MIT" - }, - "node_modules/@oxc-project/types": { - "version": "0.149.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.149.0.tgz", - "integrity": "sha512-Efcc+iF0j3Bf67YjEqIqWXbX5XddXoK/Mw4K1/JuXwRCZ8N16VR7iT23nlCc9XrveFVh/E5Rqs2StT0V8v9LdA==", - "dev": true, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/oxc-project" + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@playwright/test": { - "version": "1.63.0", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.63.0.tgz", - "integrity": "sha512-oxMK4vllB9RK5NQ2l1pq1IfOf2AvnEuj/vYGDj0H2nMtmtZpKtCwt/l00GEO6xjGfpBNAvjovvYdCm50dRQkpQ==", - "dev": true, - "license": "Apache-2.0", + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.29.7.tgz", + "integrity": "sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg==", + "license": "MIT", "dependencies": { - "playwright": "1.63.0" + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-member-expression-to-functions": "^7.29.7", + "@babel/helper-optimise-call-expression": "^7.29.7", + "@babel/helper-replace-supers": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", + "@babel/traverse": "^7.29.7", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", "bin": { - "playwright": "cli.js" + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.29.7.tgz", + "integrity": "sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { - "node": ">=20" + "node": ">=6.9.0" } }, - "node_modules/@rolldown/binding-android-arm-eabi": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.8.tgz", - "integrity": "sha512-tN5aztYkKCte4i5SIrrz5yK/HMjEuCqCSCJa418jOV8tZ1cBY3YF2otxB1ktPxzsLA1BeTqwapK0bfjxNvHJVw==", - "cpu": [ - "arm" - ], - "dev": true, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", "license": "MIT", - "optional": true, - "os": [ - "android" - ], + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=6.9.0" } }, - "node_modules/@rolldown/binding-android-arm64": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.8.tgz", - "integrity": "sha512-dIYTWl9XprMUiQFoc55KUyk/oS8SKYH3zFl0LTR7RT0Xj4hgSVyuJcroH8JUu8RcpF8fTB6E0aOwCkZoYPcDSQ==", - "cpu": [ - "arm64" - ], - "dev": true, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", "license": "MIT", - "optional": true, - "os": [ - "android" - ], + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.8.tgz", - "integrity": "sha512-PCSDQGXD2IyTEFrcgPyBM8jJuGmrbCMuoIOXdbEGVemruKACXoLQJrb+A45Z0L5t1RQkdfJprAYPkikbh7dzdA==", - "cpu": [ - "arm64" - ], - "dev": true, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.29.7.tgz", + "integrity": "sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong==", "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], + "dependencies": { + "@babel/types": "^7.29.7" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=6.9.0" } }, - "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.8.tgz", - "integrity": "sha512-Uk7lRsGhPFHVX/sAUC6D5H9Ol30dFHd6iquokll2th3LpdJ3F5CzQB+7DHn0Ri2mG+U7k2zXiPHDrwZenXhwSA==", - "cpu": [ - "x64" - ], - "dev": true, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=6.9.0" } }, - "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.8.tgz", - "integrity": "sha512-DjszaTEVogPqA5bYzsEeqDCQxbcp2fexQwKcRspYji2yzR68fCf+e4fx6kBSRDwX5/brZaHw/hWS9+A/+/w9sQ==", - "cpu": [ - "x64" - ], - "dev": true, + "node_modules/@babel/helper-replace-supers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.29.7.tgz", + "integrity": "sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ==", "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.29.7", + "@babel/helper-optimise-call-expression": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.8.tgz", - "integrity": "sha512-zmwa7FTmdzB6aaEEuuls18H6Ap5JmJPSoPTuXixeJZV6tG40SyLkApQtz1g8ptZtiEKqj9OM0oNLPh1AgvE31Q==", - "cpu": [ - "arm" - ], - "dev": true, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.29.7.tgz", + "integrity": "sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=6.9.0" } }, - "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.8.tgz", - "integrity": "sha512-KdYQDPHwJVnbFwdTGMgxsI9SqblBlz6STGM+w1We/d5B8OWWidYH0MwkU/uA1wM5fIpO2MkOVxXrNzzuZhw9ew==", - "cpu": [ - "arm64" - ], - "dev": true, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=6.9.0" } }, - "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.8.tgz", - "integrity": "sha512-jFJTifHnNPY+yzOoNZQfSIysrVyXzEQPhPnOUjmD1bcQGHH6s7c8cViKWar8YplQImE5N9JRqMCLrM2CdxOrZA==", - "cpu": [ - "arm64" - ], - "dev": true, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=6.9.0" } }, - "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.8.tgz", - "integrity": "sha512-FhiOziBDWPBjbcmRzfLyIJnaP7AVMFXT7YCXPjXxj7wKU3vx24RjrCNN/zjvVa+N2vVoHJwCoUBvsrN/DG3zIA==", - "cpu": [ - "ppc64" - ], - "dev": true, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=6.9.0" } }, - "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.8.tgz", - "integrity": "sha512-WnHfADMzOV2Y55wlx1hzzQnar/wDt/VdvWSD99r18Mz9ylNieIGOkRx3UV21h7m/eJvjySYJkO26VvGNFkwsIQ==", - "cpu": [ - "s390x" - ], - "dev": true, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=6.9.0" } }, - "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.8.tgz", - "integrity": "sha512-H9tRr5ibfXFVLxbPOseVewewFpl28zcEdjRDt2FTUZU7odxP0gEv1ki4/kGmcGOh78oRwZuuQllGLZ9zTJp84g==", - "cpu": [ - "x64" - ], - "dev": true, + "node_modules/@babel/parser": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", + "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@babel/types": "^7.29.8" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=6.0.0" } }, - "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.8.tgz", - "integrity": "sha512-UefiqfM3D6IVNlZ8tSGs9+Ejjud2T+oxO0IHADU45Y+lyEjD2dVFyZHbkfX0LUb5Zugo/oIv1eCO/KVYhgYJYA==", - "cpu": [ - "x64" - ], - "dev": true, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.29.7.tgz", + "integrity": "sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.8.tgz", - "integrity": "sha512-637Ke4kWSy6rp9cxQ9gMOXlxPgIw/c1beASV4M//3+9I4uwBVOOl74G+e3zyU3u19U7RkRl/HuewixZ/Z6+Rjg==", - "cpu": [ - "arm64" - ], - "dev": true, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.29.7.tgz", + "integrity": "sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA==", "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.8.tgz", - "integrity": "sha512-xWBkPOF1Q9k/Gv1nQXnVdLxKu74jXppuOM4Z3mnypVUJJJwLsMl7hNJGRAUJoG8A5MgOI1ACKM+wBFxSJzKy4A==", - "cpu": [ - "arm64" - ], - "dev": true, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.29.7.tgz", + "integrity": "sha512-j0vCldybPC5b5dwCQOJ21uKtHzt7hxLygJTg9eF1ScfaikEDNfzn94XoW5Fi+seBR0nCyL23xaBFFkq7dTM8XQ==", "license": "MIT", - "optional": true, - "os": [ - "win32" - ], + "dependencies": { + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.8.tgz", - "integrity": "sha512-uz2ZvfgXbxqNwijjjbxrnvALwpyODDcgc1T1N8N3rf/DXKQmaFwmB4LX4yyjggpwN2obdQLb2rgirX5ffCWYng==", - "cpu": [ - "x64" - ], - "dev": true, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.29.7.tgz", + "integrity": "sha512-jK52h8LaLc7JarhQV2ofeFMts4H7vnOXnqZNA6fYglBTZewRBE51KWt3BUltW1P+KoPsYkHoJeXePuz4zo2LMw==", "license": "MIT", - "optional": true, - "os": [ - "win32" - ], + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", + "@babel/plugin-syntax-typescript": "^7.29.7" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", - "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@synixir/client": { - "resolved": "packages/client", - "link": true - }, - "node_modules/@tiptap/core": { - "version": "3.31.3", - "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.31.3.tgz", - "integrity": "sha512-Cz50pvciQrxdSxgTkHOVz0uD0Yl/8Xt0QatGD6ILm47jW8EzyHR9RkUGs/D5IqzXKuVPntfw1ttaT926vXfiRg==", - "license": "MIT", - "peer": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" + "node": ">=6.9.0" }, "peerDependencies": { - "@tiptap/pm": "3.31.3" + "@babel/core": "^7.0.0-0" } }, - "node_modules/@tiptap/extension-blockquote": { - "version": "3.31.3", - "resolved": "https://registry.npmjs.org/@tiptap/extension-blockquote/-/extension-blockquote-3.31.3.tgz", - "integrity": "sha512-fyY2XMbyDDDfOTQ1Qdrnqa1qwC9DWE4n7AfE0EKQI0G8MfLV8RaDlLDcOZDJ9JbMPY7/Gx7EjyK4NKxSW9n2hQ==", + "node_modules/@babel/preset-typescript": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.29.7.tgz", + "integrity": "sha512-/Foi8vKY2EVbed/1eZx0gJEEwHAIxogrySI7rULcRIvhZzbvoE/b5qG5Ghc0WKAFKOHA9SD1x7RsFlOYdutIiQ==", "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "@babel/plugin-syntax-jsx": "^7.29.7", + "@babel/plugin-transform-modules-commonjs": "^7.29.7", + "@babel/plugin-transform-typescript": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { - "@tiptap/core": "3.31.3", - "@tiptap/pm": "3.31.3" + "@babel/core": "^7.0.0-0" } }, - "node_modules/@tiptap/extension-bold": { - "version": "3.31.3", - "resolved": "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-3.31.3.tgz", - "integrity": "sha512-dIuYhKk8TitKU/FeDpoTeWZhU42YgDN5npgWNjAmMmRktPdoxnH3/wGSiwXlqZgJWjehN7kPWDePWpJeAImGpQ==", + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" }, - "peerDependencies": { - "@tiptap/core": "3.31.3" + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@tiptap/extension-bullet-list": { - "version": "3.31.3", - "resolved": "https://registry.npmjs.org/@tiptap/extension-bullet-list/-/extension-bullet-list-3.31.3.tgz", - "integrity": "sha512-qEyyoPapPef4LO8XKaN83bxtaNzkJ4kFn/IxLnEKd4BJ3Mvi4MH2yJYlyDqwFnMoev4pMA1zBHRAt/C0THRmZw==", + "node_modules/@babel/traverse": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz", + "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==", "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.8", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.8", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8", + "debug": "^4.3.1" }, - "peerDependencies": { - "@tiptap/extension-list": "3.31.3" + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@tiptap/extension-code": { - "version": "3.31.3", - "resolved": "https://registry.npmjs.org/@tiptap/extension-code/-/extension-code-3.31.3.tgz", - "integrity": "sha512-SzxOqchrD2AcN3uT67PjKmRFEMOU3vNNiwNaamZJUbZrI6Hmy+bvdHJrc3jrIddyyCrAtsnAfRI0cmorW1jGfg==", + "node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" }, - "peerDependencies": { - "@tiptap/core": "3.31.3" + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@tiptap/extension-code-block": { - "version": "3.31.3", - "resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-3.31.3.tgz", - "integrity": "sha512-nvknt4FhyJQjYcvxptmeUlFsIAc8ibua3E5BN4Pim374/9RWepH4cdE9X0/qUTtguHElLx0iKtSIY3rW2qGTFA==", + "node_modules/@codemirror/commands": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.11.0.tgz", + "integrity": "sha512-/K4Rl5BN0OtTiPWmJCdqODu38XnDMsDxKY5rgrPnCkutPTJf2wVbkoixLfealF5Kwse/s8P8M5jAiURiwSwnFA==", "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "3.31.3", - "@tiptap/pm": "3.31.3" + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.7.0", + "@codemirror/view": "^6.27.0", + "@lezer/common": "^1.1.0" } }, - "node_modules/@tiptap/extension-collaboration": { - "version": "3.31.3", - "resolved": "https://registry.npmjs.org/@tiptap/extension-collaboration/-/extension-collaboration-3.31.3.tgz", - "integrity": "sha512-JAwOXjeeDYghrPcK57dtvxwn06zcz50mxSSk4PaSdLxM8tkw8+udx+51ewqKKd3WlFof4zyMwUEbO/DGaUdEvw==", + "node_modules/@codemirror/language": { + "version": "6.12.4", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.12.4.tgz", + "integrity": "sha512-1q4PaT+o6PbgpkJt4Q8Fv5XJxTy4FUZ4MWETtyiDw3J0Pyr9E2vqcKL+k9wcvjNTIsauxvE7OfmWj3FRPHQ76A==", "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "3.31.3", - "@tiptap/pm": "3.31.3", - "@tiptap/y-tiptap": "^3.0.7", - "yjs": "^13" + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.23.0", + "@lezer/common": "^1.5.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0", + "style-mod": "^4.0.0" } }, - "node_modules/@tiptap/extension-document": { - "version": "3.31.3", - "resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-3.31.3.tgz", - "integrity": "sha512-EexgmqnyDNyGlISxo7SMrp5MygpJYmqD+0cY5jB6L1U6L4CpKKRWUt8OO1sWzEHDE1+TTvwt+WIFoIWAziOtEA==", + "node_modules/@codemirror/state": { + "version": "6.7.4", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.7.4.tgz", + "integrity": "sha512-QhQIVRY+xHZDxwOSFrJ1eUMapJBUID3IdeAjf7dHO7zBUzSkyooHiodnalz5MG3iHzwixKMlAAyn7244y537EA==", "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "3.31.3" + "peer": true, + "dependencies": { + "@marijn/find-cluster-break": "^1.0.0" } }, - "node_modules/@tiptap/extension-dropcursor": { - "version": "3.31.3", - "resolved": "https://registry.npmjs.org/@tiptap/extension-dropcursor/-/extension-dropcursor-3.31.3.tgz", - "integrity": "sha512-NWomSfu5CSC7VacnMSDzKT8qm66SzMfZwVPEtwY5bPpRTJgTiT1rNK0neDrrzfMN27MfylGyKWWf7Q5Qf8w/fg==", + "node_modules/@codemirror/view": { + "version": "6.43.11", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.43.11.tgz", + "integrity": "sha512-2+esucbQX6wB2JYi1eDvdCPFTA31BN8oSy6xCmk3G6CloV11yOvEjYk+gH7kLrP0MuHG94E8WDhjs5oMiu3+Wg==", "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" + "peer": true, + "dependencies": { + "@codemirror/state": "^6.7.0", + "crelt": "^1.0.6", + "style-mod": "^4.1.0", + "w3c-keyname": "^2.2.4" + } + }, + "node_modules/@dotenvx/dotenvx": { + "version": "1.75.1", + "resolved": "https://registry.npmjs.org/@dotenvx/dotenvx/-/dotenvx-1.75.1.tgz", + "integrity": "sha512-/BITOC9dmS/edY2zQwZNicQ059O6RKabtQfyEafV0nGtfYRNHYy1DIPiYVcov40+tob9hfmBnbR963dS+EQ1DQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@dotenvx/primitives": "^0.8.0", + "commander": "^11.1.0", + "conf": "^10.2.0", + "dotenv": "^17.2.1", + "enquirer": "^2.4.1", + "env-paths": "^2.2.1", + "execa": "^5.1.1", + "fdir": "^6.2.0", + "ignore": "^5.3.0", + "object-treeify": "1.1.33", + "open": "^8.4.2", + "picomatch": "^4.0.4", + "systeminformation": "^5.22.11", + "undici": "^7.11.0", + "which": "^4.0.0", + "yocto-spinner": "^1.1.0" }, - "peerDependencies": { - "@tiptap/extensions": "3.31.3" + "bin": { + "dotenvx": "src/cli/dotenvx.js" + }, + "funding": { + "url": "https://dotenvx.com" } }, - "node_modules/@tiptap/extension-gapcursor": { - "version": "3.31.3", - "resolved": "https://registry.npmjs.org/@tiptap/extension-gapcursor/-/extension-gapcursor-3.31.3.tgz", - "integrity": "sha512-EBXKb1FrVStsNYCcRGtd9jmzveCvR+eqgg1rVqoONrqFK6U7bga6LN+1dMKroP1kliDVgveYkP3vRYxqw+rFqg==", + "node_modules/@dotenvx/dotenvx/node_modules/commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/extensions": "3.31.3" + "engines": { + "node": ">=16" } }, - "node_modules/@tiptap/extension-hard-break": { - "version": "3.31.3", - "resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-3.31.3.tgz", - "integrity": "sha512-QAdCvNO4+yW9ATwsrej11NTkDYFqPLIEQr3ARNrKOK1qaiS7A0fia2SEukb/hrkP3A6mbozhoQt2r2RGUf/DpQ==", + "node_modules/@dotenvx/dotenvx/node_modules/define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "3.31.3" + "engines": { + "node": ">=8" } }, - "node_modules/@tiptap/extension-heading": { - "version": "3.31.3", - "resolved": "https://registry.npmjs.org/@tiptap/extension-heading/-/extension-heading-3.31.3.tgz", - "integrity": "sha512-rk5VHMAeQcg06SLauN6EGdD2jc0O2qY8QkZYPd0LxNvLblw2BxBx+lxUQSYwLAT9Ie5914gKIK2YbRyO2Ts3ig==", + "node_modules/@dotenvx/dotenvx/node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" }, - "peerDependencies": { - "@tiptap/core": "3.31.3" - } - }, - "node_modules/@tiptap/extension-horizontal-rule": { - "version": "3.31.3", - "resolved": "https://registry.npmjs.org/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-3.31.3.tgz", - "integrity": "sha512-YnHGy2KShRwvCseAmmxl9VP7R0qaj8QMp3DA6DJWZqp7r5gLGvDkAqhedxqqefqsE4Y43hjkBdjtB9Ce78LkIw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" + "engines": { + "node": ">=10" }, - "peerDependencies": { - "@tiptap/core": "3.31.3", - "@tiptap/pm": "3.31.3" - } - }, - "node_modules/@tiptap/extension-italic": { - "version": "3.31.3", - "resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-3.31.3.tgz", - "integrity": "sha512-ibGvdvAPyfxBMUVNRI43eb9h2/Jka1MRG5GtnGqbcCX/2+Y/y0EOfFrPQPkuYphiWQYyu9+FzPKMB/pjuaLuKQ==", - "license": "MIT", "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "3.31.3" + "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/@tiptap/extension-link": { - "version": "3.31.3", - "resolved": "https://registry.npmjs.org/@tiptap/extension-link/-/extension-link-3.31.3.tgz", - "integrity": "sha512-986wOQzTL9Zr5lf84LCLpm+YOms8A0K39/8DVoqRfebqcOe0/eq4bnztmAlfabOd+kJY92g3AgZERFUx/w+dcw==", + "node_modules/@dotenvx/dotenvx/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "license": "MIT", - "dependencies": { - "linkifyjs": "^4.3.3" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" + "engines": { + "node": ">=10" }, - "peerDependencies": { - "@tiptap/core": "3.31.3", - "@tiptap/pm": "3.31.3" - } - }, - "node_modules/@tiptap/extension-list": { - "version": "3.31.3", - "resolved": "https://registry.npmjs.org/@tiptap/extension-list/-/extension-list-3.31.3.tgz", - "integrity": "sha512-LoveGnC0FVdCV4jUNBaG1ZA+KWE07+adzV3kGy6uUYFcJEjbVUHTnPDrBOob3IwOSO3sCwIkvQb6EVYeXn/4yg==", - "license": "MIT", - "peer": true, "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "3.31.3", - "@tiptap/pm": "3.31.3" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@tiptap/extension-list-item": { - "version": "3.31.3", - "resolved": "https://registry.npmjs.org/@tiptap/extension-list-item/-/extension-list-item-3.31.3.tgz", - "integrity": "sha512-4QlKOriJJMvg95QJTEsy9BUYPBQ6UvJyb8WURRwdUtQUkkqb8h32lg/eyQUv2FzW9IT1AdUyNZfVaxH64kRfQA==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/extension-list": "3.31.3" + "node_modules/@dotenvx/dotenvx/node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" } }, - "node_modules/@tiptap/extension-list-keymap": { - "version": "3.31.3", - "resolved": "https://registry.npmjs.org/@tiptap/extension-list-keymap/-/extension-list-keymap-3.31.3.tgz", - "integrity": "sha512-If8UOEdDZbPJU6iYTvLtH6DOp2KBy6BKxg9UELL1AevVetGHEF/7lW8hP50Gn1tHMVpPRqmhSzVrJRpEJJgb/Q==", + "node_modules/@dotenvx/dotenvx/node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" + "bin": { + "is-docker": "cli.js" }, - "peerDependencies": { - "@tiptap/extension-list": "3.31.3" - } - }, - "node_modules/@tiptap/extension-ordered-list": { - "version": "3.31.3", - "resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-3.31.3.tgz", - "integrity": "sha512-mp3g11NgA/PYu8rj7J7Ez3l4qBy6WfTSmHIG4PZvEGG5w2oUAIkgb9DU7nPPzjmeme27oazFYZw+AtZA0+u4tw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" + "engines": { + "node": ">=8" }, - "peerDependencies": { - "@tiptap/extension-list": "3.31.3" - } - }, - "node_modules/@tiptap/extension-paragraph": { - "version": "3.31.3", - "resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-3.31.3.tgz", - "integrity": "sha512-+iPku7wJfy5hbNDNLX8dveFtYVsZMmh7vztjuq8hT3mipSC4IByDehDbU8fzUCjfXiEzmI7mQn7c8LGmHULuzA==", - "license": "MIT", "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "3.31.3" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@tiptap/extension-strike": { - "version": "3.31.3", - "resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-3.31.3.tgz", - "integrity": "sha512-G29bhKttYwcKHT+BI6emWVFol3RO/gUXxQVcmr/iT8LXXy7j8J6HFUnsKM+Kg5YlP1rxMRgsa65dbrQVahZi0A==", + "node_modules/@dotenvx/dotenvx/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" + "engines": { + "node": ">=8" }, - "peerDependencies": { - "@tiptap/core": "3.31.3" - } - }, - "node_modules/@tiptap/extension-text": { - "version": "3.31.3", - "resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-3.31.3.tgz", - "integrity": "sha512-gdsWtF+taeaCu6V+5Ct10fGo0ACUy1GnYtbb+mcathBt8OqbT+Ws60p/yEmKesBDz2Hn+B5IcWy6+2BBZl5ZTg==", - "license": "MIT", "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "3.31.3" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@tiptap/extension-underline": { - "version": "3.31.3", - "resolved": "https://registry.npmjs.org/@tiptap/extension-underline/-/extension-underline-3.31.3.tgz", - "integrity": "sha512-HghdJaOwRqYzsAxqSyNyb+IWyOMcdCl8IoiBETA9BZCJAqdXzFLcuWp7CqCqJPam9dgkWosSoHqTCAO4nTBfpw==", + "node_modules/@dotenvx/dotenvx/node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" + "dependencies": { + "is-docker": "^2.0.0" }, - "peerDependencies": { - "@tiptap/core": "3.31.3" + "engines": { + "node": ">=8" } }, - "node_modules/@tiptap/extensions": { - "version": "3.31.3", - "resolved": "https://registry.npmjs.org/@tiptap/extensions/-/extensions-3.31.3.tgz", - "integrity": "sha512-8sJNPGGUe8f3aDojcOW5cfVL7I5NrBbE0UWxG08qoi9Tea6qWbvQJsCR9tsrOapr/DaLr3kpbGZ9s1gEEfcNcA==", + "node_modules/@dotenvx/dotenvx/node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", "license": "MIT", - "peer": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" + "dependencies": { + "path-key": "^3.0.0" }, - "peerDependencies": { - "@tiptap/core": "3.31.3", - "@tiptap/pm": "3.31.3" + "engines": { + "node": ">=8" } }, - "node_modules/@tiptap/pm": { - "version": "3.31.3", - "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-3.31.3.tgz", - "integrity": "sha512-sZime0SWsz/k62W2WvHx5Ig7G2h7kVhrrmnqy+wEgIHfDwEfOlelRjaWCiBCFlF7dxGUntJusCh9FxlLhni0Ag==", + "node_modules/@dotenvx/dotenvx/node_modules/open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", "license": "MIT", - "peer": true, "dependencies": { - "prosemirror-changeset": "^2.4.1", - "prosemirror-commands": "^1.7.1", - "prosemirror-dropcursor": "^1.8.2", - "prosemirror-gapcursor": "^1.4.1", - "prosemirror-history": "^1.5.0", - "prosemirror-inputrules": "^1.5.1", - "prosemirror-keymap": "^1.2.3", - "prosemirror-model": "^1.25.11", - "prosemirror-schema-list": "^1.5.1", - "prosemirror-state": "^1.4.4", - "prosemirror-tables": "^1.8.5", - "prosemirror-transform": "^1.12.0", - "prosemirror-view": "^1.42.3" + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@tiptap/starter-kit": { - "version": "3.31.3", - "resolved": "https://registry.npmjs.org/@tiptap/starter-kit/-/starter-kit-3.31.3.tgz", - "integrity": "sha512-WKof9RewdmGHvWJ1wn0/HVNG2mV+HOgVRyJkKekuM9fgr6BZAAH/xZsWE1eon+94JnQ+KtK2ThydXQM/qc6b2A==", + "node_modules/@dotenvx/dotenvx/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC" + }, + "node_modules/@dotenvx/dotenvx/node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", "license": "MIT", - "dependencies": { - "@tiptap/core": "3.31.3", - "@tiptap/extension-blockquote": "3.31.3", - "@tiptap/extension-bold": "3.31.3", - "@tiptap/extension-bullet-list": "3.31.3", - "@tiptap/extension-code": "3.31.3", - "@tiptap/extension-code-block": "3.31.3", - "@tiptap/extension-document": "3.31.3", - "@tiptap/extension-dropcursor": "3.31.3", - "@tiptap/extension-gapcursor": "3.31.3", - "@tiptap/extension-hard-break": "3.31.3", - "@tiptap/extension-heading": "3.31.3", - "@tiptap/extension-horizontal-rule": "3.31.3", - "@tiptap/extension-italic": "3.31.3", - "@tiptap/extension-link": "3.31.3", - "@tiptap/extension-list": "3.31.3", - "@tiptap/extension-list-item": "3.31.3", - "@tiptap/extension-list-keymap": "3.31.3", - "@tiptap/extension-ordered-list": "3.31.3", - "@tiptap/extension-paragraph": "3.31.3", - "@tiptap/extension-strike": "3.31.3", - "@tiptap/extension-text": "3.31.3", - "@tiptap/extension-underline": "3.31.3", - "@tiptap/extensions": "3.31.3", - "@tiptap/pm": "3.31.3" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" + "engines": { + "node": ">=6" } }, - "node_modules/@tiptap/y-tiptap": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/@tiptap/y-tiptap/-/y-tiptap-3.0.9.tgz", - "integrity": "sha512-7/El8NQ8R5V5MkdrOUdfj9IgZacpt0H071xNimX7B0AnYiWiKefQnMKd41neQYzo2MOXbWdN3iZ+7Z7BruzOSA==", + "node_modules/@dotenvx/primitives": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@dotenvx/primitives/-/primitives-0.8.0.tgz", + "integrity": "sha512-VYJy0uhFm9zTJ1TxBaW/pA8bjbOM/OttaNMwZ1RHG4JKyRG7DhSdiqD1ipQoAyoD22olUtxbP78W9xY3Wd11bg==", + "license": "BSD-3-Clause" + }, + "node_modules/@emnapi/runtime": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz", + "integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==", "license": "MIT", - "peer": true, + "optional": true, "dependencies": { - "lib0": "^0.2.100" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=8.0.0" - }, - "peerDependencies": { - "prosemirror-model": "^1.7.1", - "prosemirror-state": "^1.2.3", - "prosemirror-view": "^1.9.10", - "y-protocols": "^1.0.1", - "yjs": "^13.5.38" + "tslib": "^2.4.0" } }, - "node_modules/@typescript/typescript-aix-ppc64": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-aix-ppc64/-/typescript-aix-ppc64-7.0.2.tgz", - "integrity": "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==", + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", + "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==", "cpu": [ "ppc64" ], "dev": true, - "license": "Apache-2.0", + "license": "MIT", "optional": true, "os": [ "aix" ], "engines": { - "node": ">=16.20.0" + "node": ">=18" } }, - "node_modules/@typescript/typescript-darwin-arm64": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-arm64/-/typescript-darwin-arm64-7.0.2.tgz", - "integrity": "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==", + "node_modules/@esbuild/android-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz", + "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz", + "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==", "cpu": [ "arm64" ], "dev": true, - "license": "Apache-2.0", + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz", + "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz", + "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" ], "engines": { - "node": ">=16.20.0" + "node": ">=18" } }, - "node_modules/@typescript/typescript-darwin-x64": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-x64/-/typescript-darwin-x64-7.0.2.tgz", - "integrity": "sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==", + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz", + "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==", "cpu": [ "x64" ], "dev": true, - "license": "Apache-2.0", + "license": "MIT", "optional": true, "os": [ "darwin" ], "engines": { - "node": ">=16.20.0" + "node": ">=18" } }, - "node_modules/@typescript/typescript-freebsd-arm64": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-arm64/-/typescript-freebsd-arm64-7.0.2.tgz", - "integrity": "sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==", + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz", + "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==", "cpu": [ "arm64" ], "dev": true, - "license": "Apache-2.0", + "license": "MIT", "optional": true, "os": [ "freebsd" ], "engines": { - "node": ">=16.20.0" + "node": ">=18" } }, - "node_modules/@typescript/typescript-freebsd-x64": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-x64/-/typescript-freebsd-x64-7.0.2.tgz", - "integrity": "sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==", + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz", + "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==", "cpu": [ "x64" ], "dev": true, - "license": "Apache-2.0", + "license": "MIT", "optional": true, "os": [ "freebsd" ], "engines": { - "node": ">=16.20.0" + "node": ">=18" } }, - "node_modules/@typescript/typescript-linux-arm": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm/-/typescript-linux-arm-7.0.2.tgz", - "integrity": "sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==", + "node_modules/@esbuild/linux-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz", + "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==", "cpu": [ "arm" ], "dev": true, - "license": "Apache-2.0", + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=16.20.0" + "node": ">=18" } }, - "node_modules/@typescript/typescript-linux-arm64": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm64/-/typescript-linux-arm64-7.0.2.tgz", - "integrity": "sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==", + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz", + "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==", "cpu": [ "arm64" ], "dev": true, - "license": "Apache-2.0", + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=16.20.0" + "node": ">=18" } }, - "node_modules/@typescript/typescript-linux-loong64": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-loong64/-/typescript-linux-loong64-7.0.2.tgz", - "integrity": "sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==", + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz", + "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz", + "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==", "cpu": [ "loong64" ], "dev": true, - "license": "Apache-2.0", + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=16.20.0" + "node": ">=18" } }, - "node_modules/@typescript/typescript-linux-mips64el": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-mips64el/-/typescript-linux-mips64el-7.0.2.tgz", - "integrity": "sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==", + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz", + "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==", "cpu": [ "mips64el" ], "dev": true, - "license": "Apache-2.0", + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=16.20.0" + "node": ">=18" } }, - "node_modules/@typescript/typescript-linux-ppc64": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-ppc64/-/typescript-linux-ppc64-7.0.2.tgz", - "integrity": "sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==", + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz", + "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==", "cpu": [ "ppc64" ], "dev": true, - "license": "Apache-2.0", + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=16.20.0" + "node": ">=18" } }, - "node_modules/@typescript/typescript-linux-riscv64": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-riscv64/-/typescript-linux-riscv64-7.0.2.tgz", - "integrity": "sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==", + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz", + "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==", "cpu": [ "riscv64" ], "dev": true, - "license": "Apache-2.0", + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=16.20.0" + "node": ">=18" } }, - "node_modules/@typescript/typescript-linux-s390x": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-s390x/-/typescript-linux-s390x-7.0.2.tgz", - "integrity": "sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==", + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz", + "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==", "cpu": [ "s390x" ], "dev": true, - "license": "Apache-2.0", + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=16.20.0" + "node": ">=18" } }, - "node_modules/@typescript/typescript-linux-x64": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-x64/-/typescript-linux-x64-7.0.2.tgz", - "integrity": "sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==", + "node_modules/@esbuild/linux-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz", + "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==", "cpu": [ "x64" ], "dev": true, - "license": "Apache-2.0", + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=16.20.0" + "node": ">=18" } }, - "node_modules/@typescript/typescript-netbsd-arm64": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-arm64/-/typescript-netbsd-arm64-7.0.2.tgz", - "integrity": "sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==", + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz", + "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==", "cpu": [ "arm64" ], "dev": true, - "license": "Apache-2.0", + "license": "MIT", "optional": true, "os": [ "netbsd" ], "engines": { - "node": ">=16.20.0" + "node": ">=18" } }, - "node_modules/@typescript/typescript-netbsd-x64": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-x64/-/typescript-netbsd-x64-7.0.2.tgz", - "integrity": "sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==", + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz", + "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==", "cpu": [ "x64" ], "dev": true, - "license": "Apache-2.0", + "license": "MIT", "optional": true, "os": [ "netbsd" ], "engines": { - "node": ">=16.20.0" + "node": ">=18" } }, - "node_modules/@typescript/typescript-openbsd-arm64": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-arm64/-/typescript-openbsd-arm64-7.0.2.tgz", - "integrity": "sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==", + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz", + "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==", "cpu": [ "arm64" ], "dev": true, - "license": "Apache-2.0", + "license": "MIT", "optional": true, "os": [ "openbsd" ], "engines": { - "node": ">=16.20.0" + "node": ">=18" } }, - "node_modules/@typescript/typescript-openbsd-x64": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-x64/-/typescript-openbsd-x64-7.0.2.tgz", - "integrity": "sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==", + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz", + "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==", "cpu": [ "x64" ], "dev": true, - "license": "Apache-2.0", + "license": "MIT", "optional": true, "os": [ "openbsd" ], "engines": { - "node": ">=16.20.0" + "node": ">=18" } }, - "node_modules/@typescript/typescript-sunos-x64": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-sunos-x64/-/typescript-sunos-x64-7.0.2.tgz", - "integrity": "sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==", + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz", + "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz", + "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==", "cpu": [ "x64" ], "dev": true, - "license": "Apache-2.0", + "license": "MIT", "optional": true, "os": [ "sunos" ], "engines": { - "node": ">=16.20.0" + "node": ">=18" } }, - "node_modules/@typescript/typescript-win32-arm64": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-arm64/-/typescript-win32-arm64-7.0.2.tgz", - "integrity": "sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==", + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz", + "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==", "cpu": [ "arm64" ], "dev": true, - "license": "Apache-2.0", + "license": "MIT", "optional": true, "os": [ "win32" ], "engines": { - "node": ">=16.20.0" + "node": ">=18" } }, - "node_modules/@typescript/typescript-win32-x64": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-x64/-/typescript-win32-x64-7.0.2.tgz", - "integrity": "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==", + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", + "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==", "cpu": [ - "x64" + "ia32" ], "dev": true, - "license": "Apache-2.0", + "license": "MIT", "optional": true, "os": [ "win32" ], "engines": { - "node": ">=16.20.0" + "node": ">=18" } }, - "node_modules/crelt": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.7.tgz", - "integrity": "sha512-aK6BbWfhf4U/wCcLHKPJl/xa6VkVstRaPywWtMKGwuOLc/wZTyQYuoxgvZnNsBvv7Kg3YTBQYYBCggcviQczuA==", - "license": "MIT" - }, - "node_modules/detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "node_modules/@esbuild/win32-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", + "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==", + "cpu": [ + "x64" + ], "dev": true, - "license": "Apache-2.0", + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, + "node_modules/@floating-ui/core": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.8.0.tgz", + "integrity": "sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==", "license": "MIT", - "engines": { - "node": ">=12.0.0" + "dependencies": { + "@floating-ui/utils": "^0.2.12" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.8.0.tgz", + "integrity": "sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.8.0", + "@floating-ui/utils": "^0.2.12" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.9.tgz", + "integrity": "sha512-JDjEFGCpImxDCA7JJKviA0M9+RtmJdj0m/NVU5IMgBK+AmZouAQQ7/+2GLH0GXXY0YMw9oXPB8hKdbPYg5QLYg==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.8.0" }, "peerDependencies": { - "picomatch": "^3 || ^4" + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.12.tgz", + "integrity": "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==", + "license": "MIT" + }, + "node_modules/@hono/node-server": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-2.1.1.tgz", + "integrity": "sha512-ELuehkj5VCBdgEw9zs+ivkKwyzzUCSQuE96YmiPvn1ECBoZCczbFXJLeEGMTYjphP6gydh4pHMqEYPVMYUVgQg==", + "license": "MIT", + "engines": { + "node": ">=20" }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } + "peerDependencies": { + "hono": "^4" } }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, + "node_modules/@img/colour": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", "license": "MIT", "optional": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.4.tgz", + "integrity": "sha512-Uhfl4V4lhP2nbUVF9+hyH1+luj86f1gUFeo8ALYxFoULoU+G87D43BfeMP8XHsk9boxAnCY/bf2EHwhA7MuGsA==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, "os": [ "darwin" ], "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/isomorphic.js": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/isomorphic.js/-/isomorphic.js-0.2.5.tgz", - "integrity": "sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==", - "license": "MIT", + "node": ">=20.9.0" + }, "funding": { - "type": "GitHub Sponsors ❤", - "url": "https://github.com/sponsors/dmonad" + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.3.3" } }, - "node_modules/lib0": { - "version": "0.2.117", - "resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.117.tgz", - "integrity": "sha512-DeXj9X5xDCjgKLU/7RR+/HQEVzuuEUiwldwOGsHK/sfAfELGWEyTcf0x+uOvCvK3O2zPmZePXWL85vtia6GyZw==", - "license": "MIT", - "dependencies": { - "isomorphic.js": "^0.2.4" - }, - "bin": { - "0ecdsa-generate-keypair": "bin/0ecdsa-generate-keypair.js", - "0gentesthtml": "bin/gentesthtml.js", - "0serve": "bin/0serve.js" - }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.4.tgz", + "integrity": "sha512-hWniXY3bG5qKpkKrAwPe4y+VTPmf086YQAnkxWh7uA1YrlRouWGa0M0Mxj3ZjnXFkv7/TD1bTy9lGUK26vRvWw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=16" + "node": ">=20.9.0" }, "funding": { - "type": "GitHub Sponsors ❤", - "url": "https://github.com/sponsors/dmonad" + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.3.3" } }, - "node_modules/lightningcss": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", - "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", - "dev": true, - "license": "MPL-2.0", + "node_modules/@img/sharp-freebsd-wasm32": { + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.4.tgz", + "integrity": "sha512-lIsKw/BU+kjB4eZjxrYrZmwOJYi3Ajrv66iAlBmUPyKc3HpnloevB1g3wxGD9P/5BbQ1brBGl65VRRrCvQDEqA==", + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], "dependencies": { - "detect-libc": "^2.0.3" + "@img/sharp-wasm32": "0.35.4" }, "engines": { - "node": ">= 12.0.0" + "node": ">=20.9.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-android-arm64": "1.33.0", - "lightningcss-darwin-arm64": "1.33.0", - "lightningcss-darwin-x64": "1.33.0", - "lightningcss-freebsd-x64": "1.33.0", - "lightningcss-linux-arm-gnueabihf": "1.33.0", - "lightningcss-linux-arm64-gnu": "1.33.0", - "lightningcss-linux-arm64-musl": "1.33.0", - "lightningcss-linux-x64-gnu": "1.33.0", - "lightningcss-linux-x64-musl": "1.33.0", - "lightningcss-win32-arm64-msvc": "1.33.0", - "lightningcss-win32-x64-msvc": "1.33.0" + "url": "https://opencollective.com/libvips" } }, - "node_modules/lightningcss-android-arm64": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", - "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.3.tgz", + "integrity": "sha512-suTBPTDGrI9WodccaDdwZItTSaBYASlBk1NSfElSHrUfzu3szG6lvIF58+WiFvnfzuK8ZBFS5zE00PxqxnRiPg==", "cpu": [ "arm64" ], - "dev": true, - "license": "MPL-2.0", + "license": "LGPL-3.0-or-later", "optional": true, "os": [ - "android" + "darwin" ], - "engines": { - "node": ">= 12.0.0" - }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://opencollective.com/libvips" } }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", - "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.3.tgz", + "integrity": "sha512-FVJZ5mITMobmXIz/hPDTw0EintTW5H3WfrxwLqEqjiIihlu+hVRyGrFQ60xl0Lxn7Bt3zdpevPaQi0HEzqz9fw==", "cpu": [ - "arm64" + "x64" ], - "dev": true, - "license": "MPL-2.0", + "license": "LGPL-3.0-or-later", "optional": true, "os": [ "darwin" ], - "engines": { - "node": ">= 12.0.0" - }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://opencollective.com/libvips" } }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", - "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.3.tgz", + "integrity": "sha512-3rbU4vqXXc3hY/OiXdl52xZvT0F1yEngWfvqudtPJg/KkyiaQw2DRsFrNzpmLvfavbwOq3qXn36GP8obHRULQA==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.3.tgz", + "integrity": "sha512-0DaL0A6Xu6sQSQFwe4iVCrKWU2cCTItnRsYsCdxAMm9NF6twAA9BKnoqy4hqz4+azQ0JHuA26qiUKsf1XJ/v5A==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.3.tgz", + "integrity": "sha512-cdn1OvUBwsXhbC0zSzJnNzf5MZ/mTrobawDvNXBTxe8VtqKAm0sRuEY2Evzovb/w9JMk4TvRxqt1mekSuJz64w==", + "cpu": [ + "ppc64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.3.tgz", + "integrity": "sha512-HjPVx7yKz+0lqdhDlTw1tt90wamBoxhiXpvl1XZpJLiHH4RCJ5yDTqH+VlYPv2fwFs89JFw4c1IexYOcQUi4IQ==", + "cpu": [ + "riscv64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.3.tgz", + "integrity": "sha512-neWLh+3yCNThxnfy3c4BbVBeGgt9aftno+XbT56iK28RgeDs3UOFWviLWlUu0bArYVYJaFDK+RRohbicUNCm8Q==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.3.tgz", + "integrity": "sha512-4vKmvAst9nrowcqquKFAyZJUDolUaIp8uRiN0mWFguJ1IplC9/pitXtlnnlU4aa/eJw3J7i67V+pwUL+wZGdsA==", "cpu": [ "x64" ], - "dev": true, - "license": "MPL-2.0", + "license": "LGPL-3.0-or-later", "optional": true, "os": [ - "darwin" + "linux" ], - "engines": { - "node": ">= 12.0.0" - }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://opencollective.com/libvips" } }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", - "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.3.tgz", + "integrity": "sha512-Y9kQaLMuNoB0bPYOOdcZMaseNrFpPodIWWMrx+CZyydf2xn68j9WYc6sWWRrDwNkzCQjKYfc68L7jKjGlHMibw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.3.tgz", + "integrity": "sha512-fj8Mv0HHfD1Rr+4I68+3agJynxDWtBFgicTbSOb9Bke6pIwzGcJ+RX/yHjmiEGFMCavY/dxvem7MyNaJF+wDiw==", "cpu": [ "x64" ], - "dev": true, - "license": "MPL-2.0", + "license": "LGPL-3.0-or-later", "optional": true, "os": [ - "freebsd" + "linux" ], - "engines": { - "node": ">= 12.0.0" - }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://opencollective.com/libvips" } }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", - "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", + "node_modules/@img/sharp-linux-arm": { + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.4.tgz", + "integrity": "sha512-7OAS8gI0EReKGVN2HssHlM6umJgxF5VI3xN0p9FA91p/YO+ou5hiNghLdZ5BEHztwaaK5+bLKRf8x/o2L2nk9A==", "cpu": [ "arm" ], - "dev": true, - "license": "MPL-2.0", + "license": "Apache-2.0", "optional": true, "os": [ "linux" ], "engines": { - "node": ">= 12.0.0" + "node": ">=20.9.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.3.3" } }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", - "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", + "node_modules/@img/sharp-linux-arm64": { + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.4.tgz", + "integrity": "sha512-De4jpEnAU8Hd5oT0j1G3uL4ZvTuipVMn7YC6vPaJhy6/7EwEae0SVAoBrUMYQbkLGDm85taVWwuPc1a44LTzCQ==", "cpu": [ "arm64" ], - "dev": true, - "license": "MPL-2.0", + "license": "Apache-2.0", "optional": true, "os": [ "linux" ], "engines": { - "node": ">= 12.0.0" + "node": ">=20.9.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.3.3" } }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", - "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.4.tgz", + "integrity": "sha512-2oYZJeIl4kCcMGk4ouZVjnkCtFrpQFlNEtJ6GbxzhHQchwH0NH/qEb9ykmOl29dqwMq+JhFdZn+1ak2FKhI9fQ==", "cpu": [ - "arm64" + "ppc64" ], - "dev": true, - "license": "MPL-2.0", + "license": "Apache-2.0", "optional": true, "os": [ "linux" ], "engines": { - "node": ">= 12.0.0" + "node": ">=20.9.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.3.3" } }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", - "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.4.tgz", + "integrity": "sha512-cPbNChoRURAWdebDIHSenxRpgEdy7JkPydSnUxRm9VvKD7m0/xVaR/8Fzlu81pk5nHEvHH87UZUA7cTtwnbJSA==", "cpu": [ - "x64" + "riscv64" ], - "dev": true, - "license": "MPL-2.0", + "license": "Apache-2.0", "optional": true, "os": [ "linux" ], "engines": { - "node": ">= 12.0.0" + "node": ">=20.9.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.3.3" } }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", - "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", + "node_modules/@img/sharp-linux-s390x": { + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.4.tgz", + "integrity": "sha512-RY0JFY8Fd6RonCBtHz+DvadaPkXDSI1AUn6yWL9TipqkZ1vY8w8evqdgyDFnkm4/K1ve1TvZiaePP5oSd4+WVQ==", "cpu": [ - "x64" + "s390x" ], - "dev": true, - "license": "MPL-2.0", + "license": "Apache-2.0", "optional": true, "os": [ "linux" ], "engines": { - "node": ">= 12.0.0" + "node": ">=20.9.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.3.3" } }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", - "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", + "node_modules/@img/sharp-linux-x64": { + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.4.tgz", + "integrity": "sha512-9qvvEAuk8k89TfWUoX2htWjbAMX8p+NxCppjpcg5k6xMsjhBQPTsoIh36h9Qde4WRuGpJeYnOjdosDn/cnv+OA==", "cpu": [ - "arm64" + "x64" ], - "dev": true, - "license": "MPL-2.0", + "license": "Apache-2.0", "optional": true, "os": [ - "win32" + "linux" ], "engines": { - "node": ">= 12.0.0" + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.3.3" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.4.tgz", + "integrity": "sha512-KB5jxpfWQTr0nc3xdHtWChdbifHrBGsd2SM62Eyxrl8afikm+f5qGBU75SJIZBT/S1MC8XyacdlXBMSWq6OURA==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.3.3" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.4.tgz", + "integrity": "sha512-f+eZJZIQNEEd26RPSW+76chwOf1XtA2Y/O+5ocVyLliHkeih3e+jhLVBdNTd2rS3IbNXK8+ug93Vf5ZXtF5Lxg==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.3.3" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.4.tgz", + "integrity": "sha512-zQnl4Kwp7Q6NHsENtU2T/00Zi+w3AQNwz3+UaTyVBy2FpXrzXzGjndpK61onhZjRtRpQXxCTeqw19bVyXOh7jA==", + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.11.3" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-webcontainers-wasm32": { + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.4.tgz", + "integrity": "sha512-ESfNkywmCfPNyaZjxooddJQiQ+l/nTpGEOGthxiLnIHXC/CmcBixnfwUleX9mCz9ovrUUvKMap/pm8RYbzfwaA==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/sharp-wasm32": "0.35.4" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.4.tgz", + "integrity": "sha512-iNdlBX9gLVvqe2I3uIJSIKTq6wckP/DYxZtcqxm09x5Gi24DnFBmPAWZmr60ZyYMG0xlzo6goG3670ar+RXvRw==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.4.tgz", + "integrity": "sha512-kqRsbaa5CS6KHlpxnN7WhE6vAAugXyZButpRdvDWetlv6Qv4N9WTcrWzF7tXfB9T7MsoadqdI8hmwLq6UlLvtw==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.4.tgz", + "integrity": "sha512-XtmnYhBcrORsJ4XJngyzr/EWP0hRZLAZRFaApdKuviyqF78+ylxh2y06ZmtULAMOnObJ3ucpN0AcwSWnMowTRg==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@lezer/common": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.5.2.tgz", + "integrity": "sha512-sxQE460fPZyU3sdc8lafxiPwJHBzZRy/udNFynGQky1SePYBdhkBl1kOagA9uT3pxR8K09bOrmTUqA9wb/PjSQ==", + "license": "MIT" + }, + "node_modules/@lezer/highlight": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.3.tgz", + "integrity": "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.3.0" + } + }, + "node_modules/@lezer/lr": { + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.10.tgz", + "integrity": "sha512-rnCpTIBafOx4mRp43xOxDJbFipJm/c0cia/V5TiGlhmMa+wsSdoGmUN3w5Bqrks/09Q/D4tNAmWaT8p6NRi77A==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@marijn/find-cluster-break": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.4.tgz", + "integrity": "sha512-Wy0V7+SGUjnF9/TkiM1hKVDPj7jKXduPNboMVtHTA8dySMURWqfg/JZ9E2Sq8JgSJmkl7k7Qe9FLeMSrSraWmQ==", + "license": "MIT" + }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.30.0.tgz", + "integrity": "sha512-xKd8OIzlqNzcqcNumGAa6g+PW2kjD5vrpcKOnfldAUPP3j7lnqMPwlTXQm8gF+UwH72z0lqaRbjr9hqGz0eITA==", + "license": "MIT", + "dependencies": { + "@hono/node-server": "^1.19.9 || ^2.0.5", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "express": "^5.2.1", + "express-rate-limit": "^8.2.1", + "hono": "^4.11.4", + "jose": "^6.1.3", + "json-schema-typed": "^8.0.2", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@cfworker/json-schema": "^4.1.1", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "@cfworker/json-schema": { + "optional": true + }, + "zod": { + "optional": false + } + } + }, + "node_modules/@next/env": { + "version": "16.3.5", + "resolved": "https://registry.npmjs.org/@next/env/-/env-16.3.5.tgz", + "integrity": "sha512-NWEXVDMqoEo0ktmU6u0sE2Vg0LOcsD7NnOTJNo3/fEaTfsg+F1bMIxuDmQbda4e3yTIQwVdUREF2yIuMOusKtg==", + "license": "MIT" + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "16.3.5", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.3.5.tgz", + "integrity": "sha512-pMmGgETfKvElucLHtVaeiMRbp2zUbvKx7b1yGko0liBz3cw1mKSggWN/Rp/wPz8z+E1O82u3r4L1Co+ZS5hokQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "16.3.5", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.3.5.tgz", + "integrity": "sha512-76VaGYvf6HPa5/w12yLkE3dXTn9AfdEviI79oEL3aZoAmRLc9rWitjWqyjViVysK/ht/y9YKzFkBrUdi/wGkow==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "16.3.5", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.3.5.tgz", + "integrity": "sha512-zKDELJ5jSQMHeO/hmXUQsAzagX4bQD4OiMi3pQ5FbUj+yK506oLVHnKA2YXMlbg1EHHqJYtyePOgByIDXD1lqw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "16.3.5", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.3.5.tgz", + "integrity": "sha512-7Vql0pgzCoHagv6+FNOZoqmJqA52c6zeVbhtS/47qFozO1MSx4ms7x7GHiciY8R5CDsSMKMQjJEryoJLcsBIbA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "16.3.5", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.3.5.tgz", + "integrity": "sha512-NH/xzehyHEFWE2nlcZon7TB/0+H4shfWCi7S1zka815XCOhJDYZhoeJtOYy0dh0WVRWACVXSyGNFFytoMxUhRg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "16.3.5", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.3.5.tgz", + "integrity": "sha512-lV4+EhWMfS8jcC+EH2nn/Cm5cn6XsgbE07bU9tMH8fCo0tNAqhyzi1b5wQ/Tn6NGFTvKDY65w3ZH95EjwBRAnQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "16.3.5", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.3.5.tgz", + "integrity": "sha512-/wKzAREX2RF++MhicjDbg8tGn2AiBIM0+EFeTFKoUEUbW5D6amCJehd5Z5G1H5/gxNdgnwoXMcHz24H/c2tGkQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "16.3.5", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.3.5.tgz", + "integrity": "sha512-LNdCHzgLFc+UeqMS84LzXPaeBRKyqDN9OMyFAr1OrB0XrNw78IRrEVtZvvA7245W/HsaoeVOQX9jPjPk8jojwA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.149.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.149.0.tgz", + "integrity": "sha512-Efcc+iF0j3Bf67YjEqIqWXbX5XddXoK/Mw4K1/JuXwRCZ8N16VR7iT23nlCc9XrveFVh/E5Rqs2StT0V8v9LdA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/oxc-project" + } + }, + "node_modules/@playwright/test": { + "version": "1.63.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.63.0.tgz", + "integrity": "sha512-oxMK4vllB9RK5NQ2l1pq1IfOf2AvnEuj/vYGDj0H2nMtmtZpKtCwt/l00GEO6xjGfpBNAvjovvYdCm50dRQkpQ==", + "devOptional": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "playwright": "1.63.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@radix-ui/number": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.3.tgz", + "integrity": "sha512-Road2bidD0uu/1BGDOWNdPI06g0lIRy6IF9GZcIrDK2KGItfor8IQwQa+yM2ERgHM1MmHxaxpTzk0/Jp42lNfA==", + "license": "MIT" + }, + "node_modules/@radix-ui/primitive": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.7.tgz", + "integrity": "sha512-rqWnm76nYT8HoNNqEjpgJ7Pw/DrBj5iBTrmEPo6HTX5+VJyBNOqTdv4g89G63HuR5g0AaENoAcH7Is5fF2kZ8Q==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-accessible-icon": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-accessible-icon/-/react-accessible-icon-1.1.15.tgz", + "integrity": "sha512-WTQwcAvQf5sOcuUyi90lKPbhwcvQ+j55cjrSmeaN+L2vKU3DooOvlKw2MDeiJ5IkV5N905KW0/fGojKOBhD11A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-visually-hidden": "1.2.11" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-accordion": { + "version": "1.2.20", + "resolved": "https://registry.npmjs.org/@radix-ui/react-accordion/-/react-accordion-1.2.20.tgz", + "integrity": "sha512-jDhG9FvAEnlhnjrsINbNXcUa4G+L1KqSkJSunkbKEzFRcAb52jvM0PjPxPRvhe1HNc5F5yc0yzzWeeqlH4yBIg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-collapsible": "1.1.20", + "@radix-ui/react-collection": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-controllable-state": "1.2.6" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-alert-dialog": { + "version": "1.1.23", + "resolved": "https://registry.npmjs.org/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.1.23.tgz", + "integrity": "sha512-VAYOiQRqj3GPpYJE0I9J+X8Ip05cyVlNdKOFeiGS2Ou1HHGfpl0BxOyZm6nmVDyU+W+NF3/XLzmjHmVGydhwgA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-dialog": "1.1.23", + "@radix-ui/react-primitive": "2.1.10" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-arrow": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.15.tgz", + "integrity": "sha512-v4zggRcjadnI+ClKDuijlQEW4tw3NoaeHc/PwpKnLoLLKNUG4InLegkstooLcRIUWCs+8L22dGURCVuFfOKfnA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.10" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-aspect-ratio": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-aspect-ratio/-/react-aspect-ratio-1.1.15.tgz", + "integrity": "sha512-fy+dyVR+90nelK8rqIznFlxzx7uPcGbhxH8Nfr2bHb4UfSe+e3hklOC0luK0hDwVwnRX7xTRySpsrQVeW+/oNQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.10" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-avatar": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.2.6.tgz", + "integrity": "sha512-4ULOTJ/mqy2hT9GlWa/MFHxHSvH3nJzHnZM1waNsc5Bonv7i70aNenghXmD97S6OJ81ekXONGGt4nT1r0PfEdA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-is-hydrated": "0.1.3", + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-checkbox": { + "version": "1.3.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.3.11.tgz", + "integrity": "sha512-Gnptr9pDDQxD3hgq2dtPbtrp/c2qH1mBwIzw3X/ivrMb2e1t0jMTi606fVEqFPaQR1ggXIVQWKj3P2WW9v7zGQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-size": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collapsible": { + "version": "1.1.20", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.20.tgz", + "integrity": "sha512-mcGesGplBnzN2sbvJETzpCNfSMyPnb29q1GRLU+Ib7bJrpIG2ywmRoh2V5VbA2uNvKikKUlVbAPks7JDjz4A8Q==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collection": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.15.tgz", + "integrity": "sha512-9W+B9NPF0NaaPh/1NJd3+KqsnlLqU9H7T2rvww+fp+T/evVXdNAyYcnfRQZFOjkR1ajQp3yORlqnI8soawLvNA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-slot": "1.3.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.5.tgz", + "integrity": "sha512-+48PbAAbq3didjJxa+OaWY2ZwgAKsNiRGyeHKszblZMQ+kcpd9pAaT11cMkGEie0vsOi3QdeTE6d5Fe3Gn61kA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.2.2.tgz", + "integrity": "sha512-RHCUGwKHDr0hDGg4X7ma4JG4/+12qxw8rkh5QKdDldlCvtja6nUx1Ef/8HVrJze81lEsgLQlqjzjGNHantgnQA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context-menu": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context-menu/-/react-context-menu-2.3.7.tgz", + "integrity": "sha512-CtXP35dxaB5T3zXSd+E3uHe/QpXcpYnZmxp6OaIbfthtfW4wyb77M23BG+bwIJDtsMwEP/YssdsmNyZu7jhWew==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-menu": "2.1.24", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-controllable-state": "1.2.6" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog": { + "version": "1.1.23", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.23.tgz", + "integrity": "sha512-Ksw4WeROkO4rC9k/onilX/Ao2Cr1ku1unMNH+XSCcP4jSXYu7HDsg9n4ojMjVb22XpYjAQ9qfrFlVbru1vXDUA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-dismissable-layer": "1.1.19", + "@radix-ui/react-focus-guards": "1.1.6", + "@radix-ui/react-focus-scope": "1.1.16", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-portal": "1.1.17", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-slot": "1.3.3", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-layout-effect": "1.1.4", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.7.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-direction": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.4.tgz", + "integrity": "sha512-5pzg4FGQNpExhnhT2zlrP1wZFaYCd1K0nYWoFAdcYoYK868IEigqMX3B3f8yIoRlAhAeDWciLI6ZdCKHF9P4Vg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.19.tgz", + "integrity": "sha512-8g4pfOL9HoKKLWGiypT+dphVqjFfmcXO5GBnhsG6zI+lxAx/8feQpr+1LSN8Re3hiZ+XkLNS4O9ztK11/LzQ6w==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-effect-event": "0.0.5" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dropdown-menu": { + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.24.tgz", + "integrity": "sha512-geq8l2rJkxvkXsT9RMgtUE3P8pITFpTsvYpbySi1IH4fZEABD/Gp85myayFgxk0ktljGMJnCbeFkyTusvSvv7g==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-menu": "2.1.24", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-controllable-state": "1.2.6" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.6.tgz", + "integrity": "sha512-RNOJjfZMTyBM6xYmV3IVGXkPjIhcBAuv48POevAXwrGJhkWZ9p1rFoIS1JFooPuT193AZmRsCPhpoVJxx6OPoQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.16.tgz", + "integrity": "sha512-wmRZ2WWLvmt6KHy2rNPOdPUjwq5xOHY02+m+udwJTn0aNIox/rkskAvJTyTLGhPK6KgrUjlJUJpgmx/+wFiFIQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-form": { + "version": "0.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-form/-/react-form-0.1.16.tgz", + "integrity": "sha512-Q4TLEn2A7TAypxwmd6R9EwrlXDvkfYSDMrq9/887AXAGh+G1rH+kYJKSTv+Si9Y0JPKTwKYv6PviAJosysNimA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-label": "2.1.15", + "@radix-ui/react-primitive": "2.1.10" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-hover-card": { + "version": "1.1.23", + "resolved": "https://registry.npmjs.org/@radix-ui/react-hover-card/-/react-hover-card-1.1.23.tgz", + "integrity": "sha512-H8qONfZd3ltrU3+jHCIgITbWo6e1iTKvP9DHdrvYbX48ooRM5FjEDTn16AMwdfuOGkWdZEhpl3PLL/Wk/AnHDQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-dismissable-layer": "1.1.19", + "@radix-ui/react-popper": "1.3.7", + "@radix-ui/react-portal": "1.1.17", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-controllable-state": "1.2.6" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-id": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.4.tgz", + "integrity": "sha512-TMQp2llA+RYn7JcjnrMnz7wN4pcVttPZnRZo52PLQsoLVKzNlVwUeHmfePgTgRluXFvlD3GD5g5MOVVTJCO0qA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-label": { + "version": "2.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.15.tgz", + "integrity": "sha512-o/rdYEwZTTo5tjknnPeyQFU45kUC4i/XyeDPP+HGyi6XqpOP6Zf5Ya5vh/Yfe9Id5JiuWnnAx2XqIeD3UYZt0g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.10" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu": { + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.24.tgz", + "integrity": "sha512-uW7RVuU6Lp/ZtfeY4b3kL32zccgEWvPv1+cf17ubYzHa9cL8AHokmk36cG/XEiH/smbQvumnieXX9j/e9RqJWA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-collection": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-dismissable-layer": "1.1.19", + "@radix-ui/react-focus-guards": "1.1.6", + "@radix-ui/react-focus-scope": "1.1.16", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-popper": "1.3.7", + "@radix-ui/react-portal": "1.1.17", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-roving-focus": "1.1.19", + "@radix-ui/react-slot": "1.3.3", + "@radix-ui/react-use-callback-ref": "1.1.4", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.7.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menubar": { + "version": "1.1.24", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menubar/-/react-menubar-1.1.24.tgz", + "integrity": "sha512-eeVs0vf7cuqXaM0qLQCPcufImiJNVBXdJDLu7ZGYl2732UH23Qat/foNGrr6vYV3/DdTsBqASoggUFgH14OcZA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-collection": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-menu": "2.1.24", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-roving-focus": "1.1.19", + "@radix-ui/react-use-controllable-state": "1.2.6" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-navigation-menu": { + "version": "1.2.22", + "resolved": "https://registry.npmjs.org/@radix-ui/react-navigation-menu/-/react-navigation-menu-1.2.22.tgz", + "integrity": "sha512-ou7iLEJ+yrhQndkkA4U21XIdS/CS45F4iXIkTZcb6/Ne9EMsOuDudVmCwmDnfFZZ+y1FZqXRNSIgBy+YMvZVZg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-collection": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-dismissable-layer": "1.1.19", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-layout-effect": "1.1.4", + "@radix-ui/react-use-previous": "1.1.4", + "@radix-ui/react-visually-hidden": "1.2.11" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-one-time-password-field": { + "version": "0.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-one-time-password-field/-/react-one-time-password-field-0.1.16.tgz", + "integrity": "sha512-Tj9P6ntAJEw52oq/F0AGknXR4XncxEt7XU47O3xJQOiWfLzEy3d9gtgKfvjSzGxzHkfL+VzvxGu2KTFsloJqXw==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.3", + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-collection": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-roving-focus": "1.1.19", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-effect-event": "0.0.5", + "@radix-ui/react-use-is-hydrated": "0.1.3", + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-password-toggle-field": { + "version": "0.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-password-toggle-field/-/react-password-toggle-field-0.1.11.tgz", + "integrity": "sha512-4gvFnmDXu3dgj21CqsufzIameRvlRd4SBqaWhcrlrNhRo0Y5i/49AmRJYe1fdAM3G2VNBbmin4b0D6cdQocwgw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-effect-event": "0.0.5", + "@radix-ui/react-use-is-hydrated": "0.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover": { + "version": "1.1.23", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.23.tgz", + "integrity": "sha512-mw58MrBlyHWFisTOYignD0vf/3gdcgAR+9of1s9G/38CbFiUwH1nCDkc0AUM9IrXFgN5Ue8n45j9WCgyM1sbiQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-dismissable-layer": "1.1.19", + "@radix-ui/react-focus-guards": "1.1.6", + "@radix-ui/react-focus-scope": "1.1.16", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-popper": "1.3.7", + "@radix-ui/react-portal": "1.1.17", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-slot": "1.3.3", + "@radix-ui/react-use-controllable-state": "1.2.6", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.7.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popper": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.3.7.tgz", + "integrity": "sha512-UsJrrd7w4wuKKTdvd/DNERVlwSlUcyXzjhyDwBk+3aPOsCjOY6ZSbxuw8E6lZTjjfP8Cpd0J8VVkrYUWyGYXyg==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-layout-effect": "1.1.4", + "@radix-ui/react-use-rect": "1.1.4", + "@radix-ui/react-use-size": "1.1.4", + "@radix-ui/rect": "1.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal": { + "version": "1.1.17", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.17.tgz", + "integrity": "sha512-vKQLcWypUnwZVvfV7UkGahH2g6ySe8M8R+zYBwPrv5byZ9QAW6cQVvNKo7GgmD+p8aYb6D9JBuvy8/WhOno2wQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-presence": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.10.tgz", + "integrity": "sha512-3wyzCQ6+ubRA+D4uv9m95JYLXxmOHp05qjrkjeA7uKHHtjpPggQzc6DAb0URl7j67oR0K2foO4ip27TiX037Bw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.10.tgz", + "integrity": "sha512-MucOnzh6hR5mid6VpkbglRAMYMjKLqRnGBbjXkzjK52fuQDd1qbkx78a5P40mkcnVXJdEVxm26E9OPAiUq7nBg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.3.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-progress": { + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.16.tgz", + "integrity": "sha512-5XnomAsoZZCY+KNTxbIghpGqPruZvKFNlvcAljVAOdDRDsH4/OZQxhtwo5wdtoDM5R6MhJBb2sPnDuRFep3lzg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-primitive": "2.1.10" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-radio-group": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.4.7.tgz", + "integrity": "sha512-cgYFEkntCxppHZgtSZ+7vh0wbZQ+IC7PPMw8DSnRG27B6kDd32/Zw0OJt7dGDigCoprMuWHjg2PvUn3PYvPFoQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-roving-focus": "1.1.19", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-size": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.19.tgz", + "integrity": "sha512-V9jI6hDjT7l3jsCQD9bLNvDLM3tH/gdbOTp7Tefp3hbbgCGQoK7tUvrWiRlcoBHIZ809ElXwNQwVo0B98LuTXQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-collection": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-is-hydrated": "0.1.3", + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-scroll-area": { + "version": "1.2.18", + "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.18.tgz", + "integrity": "sha512-Zn5Cd171wxsO3Dfg8HaW6RifTb9CYTKQJHs/G4+LN1GfmJpaQMZQyQxMprVPHpaz7QY4l9BxK2JwQuzHsXC8nA==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.3", + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.3.7.tgz", + "integrity": "sha512-WFGImkmbzcfxeIwq/+4HvRN0pizBwbwQUED4I13ezQsDdfl38ZntN6TmR8XaSzPBqoCToe8rF75j6NPNDSzhbg==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.3", + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-collection": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-dismissable-layer": "1.1.19", + "@radix-ui/react-focus-guards": "1.1.6", + "@radix-ui/react-focus-scope": "1.1.16", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-popper": "1.3.7", + "@radix-ui/react-portal": "1.1.17", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-slot": "1.3.3", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-layout-effect": "1.1.4", + "@radix-ui/react-use-previous": "1.1.4", + "@radix-ui/react-visually-hidden": "1.2.11", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.7.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-separator": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.15.tgz", + "integrity": "sha512-jOLO4lssEzWpoDu7G+Ze4VjwMRUBt291pnZD0gmalREZipnTX3wadQo7Fy48GCTfe14/YRN6rw/rOJqrE85Wxw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.10" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slider": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slider/-/react-slider-1.4.7.tgz", + "integrity": "sha512-mTSLf1GC/C0moWjTbvCM6Qn/gBjvlFt1azuWF2v7MN5C3Zq2U2J2lN3ZEYkpujuOU5Ro7A28wkviSxaKnG0BYg==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.3", + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-collection": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-layout-effect": "1.1.4", + "@radix-ui/react-use-previous": "1.1.4", + "@radix-ui/react-use-size": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.3.3.tgz", + "integrity": "sha512-qx7oqnYbxnK9kYI9m317qmFmEgo6ywqWvbTogdj7cL9p3/yx4M48p7Rnw5z3H890cL/ow/EeWJsuTykeZVXP5Q==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.5" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-switch": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.3.7.tgz", + "integrity": "sha512-48tB/4dn2UVLBCYhTu9AuR63IHl73l/qLbLgxd86noTUor4/K4LFDAcYjK+isP5313qxaFpjPVogE7+Y0/V3Kw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-size": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tabs": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.21.tgz", + "integrity": "sha512-UKxJlZid7FVtsk/WTxj4i4uSEgj2Au+KBbS7SQyTlzMhhn+86Cz3tISZdTa87bfEfcuvZezf2ZsxD4xuEKtkog==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-roving-focus": "1.1.19", + "@radix-ui/react-use-controllable-state": "1.2.6" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toast": { + "version": "1.2.23", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.23.tgz", + "integrity": "sha512-ofhyAsYaocRGOs/n0XWdUOSVzEAG6BfrMVM8z0c0kLEWY38w/0WuMFPTJP/HVaZPYkMvHZoKIIhNcjbTCBILPg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-collection": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-dismissable-layer": "1.1.19", + "@radix-ui/react-portal": "1.1.17", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-layout-effect": "1.1.4", + "@radix-ui/react-visually-hidden": "1.2.11" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toggle": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.18.tgz", + "integrity": "sha512-7lonPlKfSacd20GlOBx2ltuVKz9oqWYZz+oMQyOltw6t1y2nyftj2ZmwwUHYn49kqfDWcp8dNZm5NgV+5Z+mug==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-controllable-state": "1.2.6" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toggle-group": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle-group/-/react-toggle-group-1.1.19.tgz", + "integrity": "sha512-OtnwuSVjd1Ofi+AdnvhsjQdyuhCDwYs1w9RyB5BN/OavXOVQo42SYqQjwUnbPnaiPFBpQ9aX70dWeee+v2oBLA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-roving-focus": "1.1.19", + "@radix-ui/react-toggle": "1.1.18", + "@radix-ui/react-use-controllable-state": "1.2.6" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toolbar": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toolbar/-/react-toolbar-1.1.19.tgz", + "integrity": "sha512-Ph0IvtYw4VB12ZnZg+YtrGs8yJQsnizwo/zu0R4Y/nWugtJzA7Pg1eWeuDR9+LSqn+xjamss+UOSOJJJ4gx8jw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-roving-focus": "1.1.19", + "@radix-ui/react-separator": "1.1.15", + "@radix-ui/react-toggle-group": "1.1.19" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.16.tgz", + "integrity": "sha512-6EamKFRRnlpdadndbZ6LMwycfwkwPte1B42hs6QA0gYhjaOKqW4PZ4pjaW9UrlDX5eVt/OjncE7BFTPL5nmZhg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-dismissable-layer": "1.1.19", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-popper": "1.3.7", + "@radix-ui/react-portal": "1.1.17", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-slot": "1.3.3", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-layout-effect": "1.1.4", + "@radix-ui/react-visually-hidden": "1.2.11" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.4.tgz", + "integrity": "sha512-R6OUY2e2fA6Yn6s+VSx5KBV6Nx8LQEhu+cz7LCej18rQ1HLyg9PSC9jP/ZNx0o6FAIK9c0F1kHylzSxKsdlkrQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.6.tgz", + "integrity": "sha512-uEQJGT97ZA/TgP/Hydw47lHu+/vQj6z/0jA+WeTbK1o9Rx45GImjpD0tc3W5ad3D6XTSR6e1yEO0FvGq6WQfVQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-use-effect-event": "0.0.5", + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-effect-event": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.5.tgz", + "integrity": "sha512-7cshFL8HGS/7HEiHH+9kL9HBwp2sa9yX18Knwek6KYWmXwM7pegMgta2AXMQKI+rq3JnfSj9x8wYqFMTdG1Jgg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.5.tgz", + "integrity": "sha512-ge3ipobwSXTj4JyVtswQ7qZj0ZHdtbGuOno/LrgAAeSxtsJ6Vs4Gz5IkPH2bmqpjcLUFoqGhA/mueuIf63UXlA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-is-hydrated": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-is-hydrated/-/react-use-is-hydrated-0.1.3.tgz", + "integrity": "sha512-umO/aJ+82CpOnhDZUTbILCQf7kU/g0iv+oGs/Q8jw7IkhWBzaEP4sA268PhFAJTFetbwp3ICc6ktpI4TqtxcIw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.4.tgz", + "integrity": "sha512-K20DkRkUwDnxEYMBPcg3Y6voLkEy5p5QQmszZgLngKKiC7dzBR/aEuK3w1qlx2JWDUNH6FluahYdgR3BP+QbYw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-previous": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.4.tgz", + "integrity": "sha512-XoSLhbRbqxFtgJoi2fNHA3C6pDlY34x508vUpUGoFZfvePfHXHbE1lC4FYFMnJWgiCRroSTw6fOsXQoVS9RwZg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-rect": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.4.tgz", + "integrity": "sha512-cSOCh6JlkmfjLyNcLiu2nB4v+nm+dkZ+Q5KHWk/soo4U7ZLiEQFKHK9/YmtBHjfCEaU43IBKQOc4/uJmCaiCTQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/rect": "1.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-size": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.4.tgz", + "integrity": "sha512-D3anSY15EJoxrihpsXI6SMrmmonnQtR2ni7arO+Lfdg3O95b9hNXxONk8jA5C8ANdF/h5HMAxejgs8PWJ6rlhw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-visually-hidden": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.11.tgz", + "integrity": "sha512-NFS86RYYZb4/exihaESBGOpMJFz8MGLAfu3mOBSGByVnVPC9JPASfYubxd/8KbkQK0sYAv8lVQDEQukDX/qXvQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.10" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/rect": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.3.tgz", + "integrity": "sha512-JtyZR+mqgBibTo8xea3B6ZRmzZiM/YeVBtUkas6zMuXjAlfIFIW2FgqeM9eLyvEaYX66vr6DJMK+4U6LV0KhNw==", + "license": "MIT" + }, + "node_modules/@rolldown/binding-android-arm-eabi": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.8.tgz", + "integrity": "sha512-tN5aztYkKCte4i5SIrrz5yK/HMjEuCqCSCJa418jOV8tZ1cBY3YF2otxB1ktPxzsLA1BeTqwapK0bfjxNvHJVw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.8.tgz", + "integrity": "sha512-dIYTWl9XprMUiQFoc55KUyk/oS8SKYH3zFl0LTR7RT0Xj4hgSVyuJcroH8JUu8RcpF8fTB6E0aOwCkZoYPcDSQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.8.tgz", + "integrity": "sha512-PCSDQGXD2IyTEFrcgPyBM8jJuGmrbCMuoIOXdbEGVemruKACXoLQJrb+A45Z0L5t1RQkdfJprAYPkikbh7dzdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.8.tgz", + "integrity": "sha512-Uk7lRsGhPFHVX/sAUC6D5H9Ol30dFHd6iquokll2th3LpdJ3F5CzQB+7DHn0Ri2mG+U7k2zXiPHDrwZenXhwSA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.8.tgz", + "integrity": "sha512-DjszaTEVogPqA5bYzsEeqDCQxbcp2fexQwKcRspYji2yzR68fCf+e4fx6kBSRDwX5/brZaHw/hWS9+A/+/w9sQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.8.tgz", + "integrity": "sha512-zmwa7FTmdzB6aaEEuuls18H6Ap5JmJPSoPTuXixeJZV6tG40SyLkApQtz1g8ptZtiEKqj9OM0oNLPh1AgvE31Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.8.tgz", + "integrity": "sha512-KdYQDPHwJVnbFwdTGMgxsI9SqblBlz6STGM+w1We/d5B8OWWidYH0MwkU/uA1wM5fIpO2MkOVxXrNzzuZhw9ew==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.8.tgz", + "integrity": "sha512-jFJTifHnNPY+yzOoNZQfSIysrVyXzEQPhPnOUjmD1bcQGHH6s7c8cViKWar8YplQImE5N9JRqMCLrM2CdxOrZA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.8.tgz", + "integrity": "sha512-FhiOziBDWPBjbcmRzfLyIJnaP7AVMFXT7YCXPjXxj7wKU3vx24RjrCNN/zjvVa+N2vVoHJwCoUBvsrN/DG3zIA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.8.tgz", + "integrity": "sha512-WnHfADMzOV2Y55wlx1hzzQnar/wDt/VdvWSD99r18Mz9ylNieIGOkRx3UV21h7m/eJvjySYJkO26VvGNFkwsIQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.8.tgz", + "integrity": "sha512-H9tRr5ibfXFVLxbPOseVewewFpl28zcEdjRDt2FTUZU7odxP0gEv1ki4/kGmcGOh78oRwZuuQllGLZ9zTJp84g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.8.tgz", + "integrity": "sha512-UefiqfM3D6IVNlZ8tSGs9+Ejjud2T+oxO0IHADU45Y+lyEjD2dVFyZHbkfX0LUb5Zugo/oIv1eCO/KVYhgYJYA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.8.tgz", + "integrity": "sha512-637Ke4kWSy6rp9cxQ9gMOXlxPgIw/c1beASV4M//3+9I4uwBVOOl74G+e3zyU3u19U7RkRl/HuewixZ/Z6+Rjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.8.tgz", + "integrity": "sha512-xWBkPOF1Q9k/Gv1nQXnVdLxKu74jXppuOM4Z3mnypVUJJJwLsMl7hNJGRAUJoG8A5MgOI1ACKM+wBFxSJzKy4A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.8.tgz", + "integrity": "sha512-uz2ZvfgXbxqNwijjjbxrnvALwpyODDcgc1T1N8N3rf/DXKQmaFwmB4LX4yyjggpwN2obdQLb2rgirX5ffCWYng==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sec-ant/readable-stream": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", + "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", + "license": "MIT" + }, + "node_modules/@sindresorhus/merge-streams": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", + "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@swc/helpers": { + "version": "0.5.23", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.23.tgz", + "integrity": "sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@synixir/client": { + "resolved": "packages/client", + "link": true + }, + "node_modules/@tailwindcss/node": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.3.tgz", + "integrity": "sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==", + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "^5.24.1", + "jiti": "^2.7.0", + "lightningcss": "1.32.0", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.3.3" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.3.tgz", + "integrity": "sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA==", + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.3.3", + "@tailwindcss/oxide-darwin-arm64": "4.3.3", + "@tailwindcss/oxide-darwin-x64": "4.3.3", + "@tailwindcss/oxide-freebsd-x64": "4.3.3", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.3", + "@tailwindcss/oxide-linux-arm64-gnu": "4.3.3", + "@tailwindcss/oxide-linux-arm64-musl": "4.3.3", + "@tailwindcss/oxide-linux-x64-gnu": "4.3.3", + "@tailwindcss/oxide-linux-x64-musl": "4.3.3", + "@tailwindcss/oxide-wasm32-wasi": "4.3.3", + "@tailwindcss/oxide-win32-arm64-msvc": "4.3.3", + "@tailwindcss/oxide-win32-x64-msvc": "4.3.3" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.3.tgz", + "integrity": "sha512-Y85A2gmPSkl5Ve5qR86GL4HT509cFqQh1aes9p3sSkyTPwt0Pppf3GkwGe4JPACcRYjgJIEhQgM6dBClnr0NYw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.3.tgz", + "integrity": "sha512-BiaWatpBcERQFDlOjRDpIVXuFK5PJez5SA4JMg6VYZdBYU+qKfV/vqjcIs+IYmtitf1xYQZTwXvU/8y4lfZUGw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.3.tgz", + "integrity": "sha512-fAeUqfV5ndhxRwai8cXGzdLvul9utWOmeTkv69unv4ZXixjn61Z+p9lCWdwOwA3TYboG3BwdVuN/RDjhBRl0mw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.3.tgz", + "integrity": "sha512-iyf5bV6+wnAlflVeEy7R25dupxTNECZN5QMI0qNT6eT+EgaGdZcKhGkr5SdoaWiLJ3spLqIY9VCeSGrwmtg4kw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.3.tgz", + "integrity": "sha512-aAYUprJAJQWWbRrPvtjdroZ56Md+JM8pMiopS6xGEwDfLhqj+2ver2p4nU4Mb3CRqcMmNBjo8KkUgcxhkzVQGQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.3.tgz", + "integrity": "sha512-nDxldcEENOxZRzC2uu9jrutZdAAQtb+8WWDCSnWL1zvBk1+FN+x6MtDViPB5AJMfttVCUhehGWus3XBPgatM/w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.3.tgz", + "integrity": "sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.3.tgz", + "integrity": "sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.3.tgz", + "integrity": "sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.3.tgz", + "integrity": "sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.11.1", + "@emnapi/runtime": "^1.11.1", + "@emnapi/wasi-threads": "^1.2.2", + "@napi-rs/wasm-runtime": "^1.1.4", + "@tybys/wasm-util": "^0.10.2", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.3.tgz", + "integrity": "sha512-3rc292Ca2ceK6Ulcc/bAVnTs/3nDtoPhyEKlgPv+yQJQi/JS/AMJlqzxvlDacL1nekbrcf6bTqp/jV4qgnPxNQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.3.tgz", + "integrity": "sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/postcss": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.3.3.tgz", + "integrity": "sha512-JTSZZGQi1AyKirbLN3azmjVzef92tcX7h+iSqPdaeStyFpGpDlKvvpxeOE8njhbUanbRwr3z8DyzhICWnMtQeg==", + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "@tailwindcss/node": "4.3.3", + "@tailwindcss/oxide": "4.3.3", + "postcss": "^8.5.16", + "tailwindcss": "4.3.3" + } + }, + "node_modules/@tiptap/core": { + "version": "3.31.3", + "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.31.3.tgz", + "integrity": "sha512-Cz50pvciQrxdSxgTkHOVz0uD0Yl/8Xt0QatGD6ILm47jW8EzyHR9RkUGs/D5IqzXKuVPntfw1ttaT926vXfiRg==", + "license": "MIT", + "peer": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/pm": "3.31.3" + } + }, + "node_modules/@tiptap/extension-blockquote": { + "version": "3.31.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-blockquote/-/extension-blockquote-3.31.3.tgz", + "integrity": "sha512-fyY2XMbyDDDfOTQ1Qdrnqa1qwC9DWE4n7AfE0EKQI0G8MfLV8RaDlLDcOZDJ9JbMPY7/Gx7EjyK4NKxSW9n2hQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "3.31.3", + "@tiptap/pm": "3.31.3" + } + }, + "node_modules/@tiptap/extension-bold": { + "version": "3.31.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-3.31.3.tgz", + "integrity": "sha512-dIuYhKk8TitKU/FeDpoTeWZhU42YgDN5npgWNjAmMmRktPdoxnH3/wGSiwXlqZgJWjehN7kPWDePWpJeAImGpQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "3.31.3" + } + }, + "node_modules/@tiptap/extension-bullet-list": { + "version": "3.31.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-bullet-list/-/extension-bullet-list-3.31.3.tgz", + "integrity": "sha512-qEyyoPapPef4LO8XKaN83bxtaNzkJ4kFn/IxLnEKd4BJ3Mvi4MH2yJYlyDqwFnMoev4pMA1zBHRAt/C0THRmZw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/extension-list": "3.31.3" + } + }, + "node_modules/@tiptap/extension-code": { + "version": "3.31.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-code/-/extension-code-3.31.3.tgz", + "integrity": "sha512-SzxOqchrD2AcN3uT67PjKmRFEMOU3vNNiwNaamZJUbZrI6Hmy+bvdHJrc3jrIddyyCrAtsnAfRI0cmorW1jGfg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "3.31.3" + } + }, + "node_modules/@tiptap/extension-code-block": { + "version": "3.31.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-3.31.3.tgz", + "integrity": "sha512-nvknt4FhyJQjYcvxptmeUlFsIAc8ibua3E5BN4Pim374/9RWepH4cdE9X0/qUTtguHElLx0iKtSIY3rW2qGTFA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "3.31.3", + "@tiptap/pm": "3.31.3" + } + }, + "node_modules/@tiptap/extension-collaboration": { + "version": "3.31.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-collaboration/-/extension-collaboration-3.31.3.tgz", + "integrity": "sha512-JAwOXjeeDYghrPcK57dtvxwn06zcz50mxSSk4PaSdLxM8tkw8+udx+51ewqKKd3WlFof4zyMwUEbO/DGaUdEvw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "3.31.3", + "@tiptap/pm": "3.31.3", + "@tiptap/y-tiptap": "^3.0.7", + "yjs": "^13" + } + }, + "node_modules/@tiptap/extension-document": { + "version": "3.31.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-3.31.3.tgz", + "integrity": "sha512-EexgmqnyDNyGlISxo7SMrp5MygpJYmqD+0cY5jB6L1U6L4CpKKRWUt8OO1sWzEHDE1+TTvwt+WIFoIWAziOtEA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "3.31.3" + } + }, + "node_modules/@tiptap/extension-dropcursor": { + "version": "3.31.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-dropcursor/-/extension-dropcursor-3.31.3.tgz", + "integrity": "sha512-NWomSfu5CSC7VacnMSDzKT8qm66SzMfZwVPEtwY5bPpRTJgTiT1rNK0neDrrzfMN27MfylGyKWWf7Q5Qf8w/fg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/extensions": "3.31.3" + } + }, + "node_modules/@tiptap/extension-gapcursor": { + "version": "3.31.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-gapcursor/-/extension-gapcursor-3.31.3.tgz", + "integrity": "sha512-EBXKb1FrVStsNYCcRGtd9jmzveCvR+eqgg1rVqoONrqFK6U7bga6LN+1dMKroP1kliDVgveYkP3vRYxqw+rFqg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/extensions": "3.31.3" + } + }, + "node_modules/@tiptap/extension-hard-break": { + "version": "3.31.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-3.31.3.tgz", + "integrity": "sha512-QAdCvNO4+yW9ATwsrej11NTkDYFqPLIEQr3ARNrKOK1qaiS7A0fia2SEukb/hrkP3A6mbozhoQt2r2RGUf/DpQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "3.31.3" + } + }, + "node_modules/@tiptap/extension-heading": { + "version": "3.31.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-heading/-/extension-heading-3.31.3.tgz", + "integrity": "sha512-rk5VHMAeQcg06SLauN6EGdD2jc0O2qY8QkZYPd0LxNvLblw2BxBx+lxUQSYwLAT9Ie5914gKIK2YbRyO2Ts3ig==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "3.31.3" + } + }, + "node_modules/@tiptap/extension-horizontal-rule": { + "version": "3.31.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-3.31.3.tgz", + "integrity": "sha512-YnHGy2KShRwvCseAmmxl9VP7R0qaj8QMp3DA6DJWZqp7r5gLGvDkAqhedxqqefqsE4Y43hjkBdjtB9Ce78LkIw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "3.31.3", + "@tiptap/pm": "3.31.3" + } + }, + "node_modules/@tiptap/extension-italic": { + "version": "3.31.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-3.31.3.tgz", + "integrity": "sha512-ibGvdvAPyfxBMUVNRI43eb9h2/Jka1MRG5GtnGqbcCX/2+Y/y0EOfFrPQPkuYphiWQYyu9+FzPKMB/pjuaLuKQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "3.31.3" + } + }, + "node_modules/@tiptap/extension-link": { + "version": "3.31.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-link/-/extension-link-3.31.3.tgz", + "integrity": "sha512-986wOQzTL9Zr5lf84LCLpm+YOms8A0K39/8DVoqRfebqcOe0/eq4bnztmAlfabOd+kJY92g3AgZERFUx/w+dcw==", + "license": "MIT", + "dependencies": { + "linkifyjs": "^4.3.3" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "3.31.3", + "@tiptap/pm": "3.31.3" + } + }, + "node_modules/@tiptap/extension-list": { + "version": "3.31.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-list/-/extension-list-3.31.3.tgz", + "integrity": "sha512-LoveGnC0FVdCV4jUNBaG1ZA+KWE07+adzV3kGy6uUYFcJEjbVUHTnPDrBOob3IwOSO3sCwIkvQb6EVYeXn/4yg==", + "license": "MIT", + "peer": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "3.31.3", + "@tiptap/pm": "3.31.3" + } + }, + "node_modules/@tiptap/extension-list-item": { + "version": "3.31.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-list-item/-/extension-list-item-3.31.3.tgz", + "integrity": "sha512-4QlKOriJJMvg95QJTEsy9BUYPBQ6UvJyb8WURRwdUtQUkkqb8h32lg/eyQUv2FzW9IT1AdUyNZfVaxH64kRfQA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/extension-list": "3.31.3" + } + }, + "node_modules/@tiptap/extension-list-keymap": { + "version": "3.31.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-list-keymap/-/extension-list-keymap-3.31.3.tgz", + "integrity": "sha512-If8UOEdDZbPJU6iYTvLtH6DOp2KBy6BKxg9UELL1AevVetGHEF/7lW8hP50Gn1tHMVpPRqmhSzVrJRpEJJgb/Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/extension-list": "3.31.3" + } + }, + "node_modules/@tiptap/extension-ordered-list": { + "version": "3.31.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-3.31.3.tgz", + "integrity": "sha512-mp3g11NgA/PYu8rj7J7Ez3l4qBy6WfTSmHIG4PZvEGG5w2oUAIkgb9DU7nPPzjmeme27oazFYZw+AtZA0+u4tw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/extension-list": "3.31.3" + } + }, + "node_modules/@tiptap/extension-paragraph": { + "version": "3.31.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-3.31.3.tgz", + "integrity": "sha512-+iPku7wJfy5hbNDNLX8dveFtYVsZMmh7vztjuq8hT3mipSC4IByDehDbU8fzUCjfXiEzmI7mQn7c8LGmHULuzA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "3.31.3" + } + }, + "node_modules/@tiptap/extension-strike": { + "version": "3.31.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-3.31.3.tgz", + "integrity": "sha512-G29bhKttYwcKHT+BI6emWVFol3RO/gUXxQVcmr/iT8LXXy7j8J6HFUnsKM+Kg5YlP1rxMRgsa65dbrQVahZi0A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "3.31.3" + } + }, + "node_modules/@tiptap/extension-text": { + "version": "3.31.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-3.31.3.tgz", + "integrity": "sha512-gdsWtF+taeaCu6V+5Ct10fGo0ACUy1GnYtbb+mcathBt8OqbT+Ws60p/yEmKesBDz2Hn+B5IcWy6+2BBZl5ZTg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "3.31.3" + } + }, + "node_modules/@tiptap/extension-underline": { + "version": "3.31.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-underline/-/extension-underline-3.31.3.tgz", + "integrity": "sha512-HghdJaOwRqYzsAxqSyNyb+IWyOMcdCl8IoiBETA9BZCJAqdXzFLcuWp7CqCqJPam9dgkWosSoHqTCAO4nTBfpw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "3.31.3" + } + }, + "node_modules/@tiptap/extensions": { + "version": "3.31.3", + "resolved": "https://registry.npmjs.org/@tiptap/extensions/-/extensions-3.31.3.tgz", + "integrity": "sha512-8sJNPGGUe8f3aDojcOW5cfVL7I5NrBbE0UWxG08qoi9Tea6qWbvQJsCR9tsrOapr/DaLr3kpbGZ9s1gEEfcNcA==", + "license": "MIT", + "peer": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "3.31.3", + "@tiptap/pm": "3.31.3" + } + }, + "node_modules/@tiptap/pm": { + "version": "3.31.3", + "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-3.31.3.tgz", + "integrity": "sha512-sZime0SWsz/k62W2WvHx5Ig7G2h7kVhrrmnqy+wEgIHfDwEfOlelRjaWCiBCFlF7dxGUntJusCh9FxlLhni0Ag==", + "license": "MIT", + "peer": true, + "dependencies": { + "prosemirror-changeset": "^2.4.1", + "prosemirror-commands": "^1.7.1", + "prosemirror-dropcursor": "^1.8.2", + "prosemirror-gapcursor": "^1.4.1", + "prosemirror-history": "^1.5.0", + "prosemirror-inputrules": "^1.5.1", + "prosemirror-keymap": "^1.2.3", + "prosemirror-model": "^1.25.11", + "prosemirror-schema-list": "^1.5.1", + "prosemirror-state": "^1.4.4", + "prosemirror-tables": "^1.8.5", + "prosemirror-transform": "^1.12.0", + "prosemirror-view": "^1.42.3" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + } + }, + "node_modules/@tiptap/starter-kit": { + "version": "3.31.3", + "resolved": "https://registry.npmjs.org/@tiptap/starter-kit/-/starter-kit-3.31.3.tgz", + "integrity": "sha512-WKof9RewdmGHvWJ1wn0/HVNG2mV+HOgVRyJkKekuM9fgr6BZAAH/xZsWE1eon+94JnQ+KtK2ThydXQM/qc6b2A==", + "license": "MIT", + "dependencies": { + "@tiptap/core": "3.31.3", + "@tiptap/extension-blockquote": "3.31.3", + "@tiptap/extension-bold": "3.31.3", + "@tiptap/extension-bullet-list": "3.31.3", + "@tiptap/extension-code": "3.31.3", + "@tiptap/extension-code-block": "3.31.3", + "@tiptap/extension-document": "3.31.3", + "@tiptap/extension-dropcursor": "3.31.3", + "@tiptap/extension-gapcursor": "3.31.3", + "@tiptap/extension-hard-break": "3.31.3", + "@tiptap/extension-heading": "3.31.3", + "@tiptap/extension-horizontal-rule": "3.31.3", + "@tiptap/extension-italic": "3.31.3", + "@tiptap/extension-link": "3.31.3", + "@tiptap/extension-list": "3.31.3", + "@tiptap/extension-list-item": "3.31.3", + "@tiptap/extension-list-keymap": "3.31.3", + "@tiptap/extension-ordered-list": "3.31.3", + "@tiptap/extension-paragraph": "3.31.3", + "@tiptap/extension-strike": "3.31.3", + "@tiptap/extension-text": "3.31.3", + "@tiptap/extension-underline": "3.31.3", + "@tiptap/extensions": "3.31.3", + "@tiptap/pm": "3.31.3" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + } + }, + "node_modules/@tiptap/y-tiptap": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@tiptap/y-tiptap/-/y-tiptap-3.0.9.tgz", + "integrity": "sha512-7/El8NQ8R5V5MkdrOUdfj9IgZacpt0H071xNimX7B0AnYiWiKefQnMKd41neQYzo2MOXbWdN3iZ+7Z7BruzOSA==", + "license": "MIT", + "peer": true, + "dependencies": { + "lib0": "^0.2.100" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=8.0.0" + }, + "peerDependencies": { + "prosemirror-model": "^1.7.1", + "prosemirror-state": "^1.2.3", + "prosemirror-view": "^1.9.10", + "y-protocols": "^1.0.1", + "yjs": "^13.5.38" + } + }, + "node_modules/@ts-morph/common": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.27.0.tgz", + "integrity": "sha512-Wf29UqxWDpc+i61k3oIOzcUfQt79PIT9y/MWfAGlrkjg6lBC1hwDECLXPVJAhWjiGbfBCxZd65F/LIZF3+jeJQ==", + "license": "MIT", + "dependencies": { + "fast-glob": "^3.3.3", + "minimatch": "^10.0.1", + "path-browserify": "^1.0.1" + } + }, + "node_modules/@types/http-proxy": { + "version": "1.17.17", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.17.tgz", + "integrity": "sha512-ED6LB+Z1AVylNTu7hdzuBqOgMnvG/ld6wGCG8wFnAzKX5uyW2K3WD52v0gnLCTK/VLpXtKckgWuyScYK6cSPaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "22.20.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.2.tgz", + "integrity": "sha512-xlvWf4Vs9n1PEVYwP1n4vvG07M6y8WgvJ2t0vbrWTmijsIHp1cS+uJ2kMIRdY3nHZK0nCYKrPeD171+SzF4/zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/phoenix": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.7.tgz", + "integrity": "sha512-oN9ive//QSBkf19rfDv45M7eZPi0eEXylht2OLEXicu5b4KoQ1OzXIw+xDSGWxSxe1JmepRR/ZH283vsu518/Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.2.18", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz", + "integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==", + "devOptional": true, + "license": "MIT", + "peer": true, + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.7.tgz", + "integrity": "sha512-I8bPpDLcHBv1qiIiXDCy71Rt8eQDKJP0sMSWJphDdAcdqiJ1sGpZamavoEIRZmYzjia9LuEb2HlYdDpmoENpvQ==", + "devOptional": true, + "license": "MIT", + "peer": true, + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@types/validate-npm-package-name": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/validate-npm-package-name/-/validate-npm-package-name-4.0.2.tgz", + "integrity": "sha512-lrpDziQipxCEeK5kWxvljWYhUvOiB2A9izZd9B2AFarYAkqZshb4lPbRs7zKEic6eGtH8V/2qJW+dPp9OtF6bw==", + "license": "MIT" + }, + "node_modules/@typescript/typescript-aix-ppc64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-aix-ppc64/-/typescript-aix-ppc64-7.0.2.tgz", + "integrity": "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-darwin-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-arm64/-/typescript-darwin-arm64-7.0.2.tgz", + "integrity": "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-darwin-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-x64/-/typescript-darwin-x64-7.0.2.tgz", + "integrity": "sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-freebsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-arm64/-/typescript-freebsd-arm64-7.0.2.tgz", + "integrity": "sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-freebsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-x64/-/typescript-freebsd-x64-7.0.2.tgz", + "integrity": "sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-arm": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm/-/typescript-linux-arm-7.0.2.tgz", + "integrity": "sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm64/-/typescript-linux-arm64-7.0.2.tgz", + "integrity": "sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-loong64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-loong64/-/typescript-linux-loong64-7.0.2.tgz", + "integrity": "sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-mips64el": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-mips64el/-/typescript-linux-mips64el-7.0.2.tgz", + "integrity": "sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-ppc64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-ppc64/-/typescript-linux-ppc64-7.0.2.tgz", + "integrity": "sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-riscv64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-riscv64/-/typescript-linux-riscv64-7.0.2.tgz", + "integrity": "sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-s390x": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-s390x/-/typescript-linux-s390x-7.0.2.tgz", + "integrity": "sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-x64/-/typescript-linux-x64-7.0.2.tgz", + "integrity": "sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-netbsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-arm64/-/typescript-netbsd-arm64-7.0.2.tgz", + "integrity": "sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-netbsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-x64/-/typescript-netbsd-x64-7.0.2.tgz", + "integrity": "sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-openbsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-arm64/-/typescript-openbsd-arm64-7.0.2.tgz", + "integrity": "sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-openbsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-x64/-/typescript-openbsd-x64-7.0.2.tgz", + "integrity": "sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-sunos-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-sunos-x64/-/typescript-sunos-x64-7.0.2.tgz", + "integrity": "sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-win32-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-arm64/-/typescript-win32-arm64-7.0.2.tgz", + "integrity": "sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-win32-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-x64/-/typescript-win32-x64-7.0.2.tgz", + "integrity": "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/aria-hidden": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", + "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ast-types": { + "version": "0.16.3", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.16.3.tgz", + "integrity": "sha512-FvWoWYfSCM6kRxCSH+MGLHIKKGRL6A6AW7Zek2O32REPQRdg131428uRTKMBYAeRd3XXAaHDS60Wpri7CdKDrA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/atomically": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/atomically/-/atomically-1.7.0.tgz", + "integrity": "sha512-Xcz9l0z7y9yQ9rdDaxlmaI4uJHf/T8g9hOEzJcsEqX2SjCj4J20uK7+ldkDHMbpJDK76wF7xEIgxc/vSlsfw5w==", + "license": "MIT", + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.11.22", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.22.tgz", + "integrity": "sha512-pWc4w51fBFd7mav43/zKRC+RI6f4yfzQoVlfvE8dECePyfkn1bzLp01Fj0QACcyCZyFhiEMyD2qScfKRWgWibA==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/body-parser": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", + "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^2.0.0", + "debug": "^4.4.3", + "http-errors": "^2.0.1", + "iconv-lite": "^0.7.2", + "on-finished": "^2.4.1", + "qs": "^6.15.2", + "raw-body": "^3.0.2", + "type-is": "^2.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/body-parser/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.9", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.9.tgz", + "integrity": "sha512-EWazOblFYUvlGZcfGhPUPmYh3nikUxBVb+y9MJun5f3hBi812X+8MSQTujLBtgK3cf51fJWbWfOjyeO954d+Eg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "baseline-browser-mapping": "^2.11.20", + "caniuse-lite": "^1.0.30001810", + "electron-to-chromium": "^1.5.420", + "node-releases": "^2.0.54", + "update-browserslist-db": "^1.3.2" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001810", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz", + "integrity": "sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/class-variance-authority": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", + "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", + "license": "Apache-2.0", + "dependencies": { + "clsx": "^2.1.1" + }, + "funding": { + "url": "https://polar.sh/cva" + } + }, + "node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "license": "MIT", + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", + "license": "MIT" + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/cn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/cn/-/cn-0.3.0.tgz", + "integrity": "sha512-qRoU0ARrZD3Bbj1wKFftEKxPjSnzTEdFkf22NVhW+zU9D/wajhoP+qUZ5DChhhSOBCH1onV12hrmGqfu1+5Z2Q==", + "license": "MIT", + "bin": { + "cn": "bin/cn.mjs" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/code-block-writer": { + "version": "13.0.3", + "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-13.0.3.tgz", + "integrity": "sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg==", + "license": "MIT" + }, + "node_modules/commander": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/conf": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/conf/-/conf-10.2.0.tgz", + "integrity": "sha512-8fLl9F04EJqjSqH+QjITQfJF8BrOVaYr1jewVgSRAEWePfxT0sku4w2hrGQ60BC/TNLGQ2pgxNlTbWQmMPFvXg==", + "license": "MIT", + "dependencies": { + "ajv": "^8.6.3", + "ajv-formats": "^2.1.1", + "atomically": "^1.7.0", + "debounce-fn": "^4.0.0", + "dot-prop": "^6.0.1", + "env-paths": "^2.2.1", + "json-schema-typed": "^7.0.3", + "onetime": "^5.1.2", + "pkg-up": "^3.1.0", + "semver": "^7.3.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/conf/node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/conf/node_modules/json-schema-typed": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-7.0.3.tgz", + "integrity": "sha512-7DE8mpG+/fVw+dTpjbxnx47TaMnDfOI1jwft9g1VybltZCduyRQPJPvc+zzKY9WPHxhPWczyFuYa6I8Mw4iU5A==", + "license": "BSD-2-Clause" + }, + "node_modules/content-disposition": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/cosmiconfig": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.2.tgz", + "integrity": "sha512-gtTZxTDau1wL7Y7zifc2dd8jHSK/k6BTx/2Xp/BpdlAdnlYWFVt7qhJqgwi7637yRwRQ3qL4ZidbB4I8tA5VOg==", + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/crelt": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.7.tgz", + "integrity": "sha512-aK6BbWfhf4U/wCcLHKPJl/xa6VkVstRaPywWtMKGwuOLc/wZTyQYuoxgvZnNsBvv7Kg3YTBQYYBCggcviQczuA==", + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cross-spawn/node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/debounce-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/debounce-fn/-/debounce-fn-4.0.0.tgz", + "integrity": "sha512-8pYCQiL9Xdcg0UPSD3d+0KMlOjp+KGU5EPwYddgzQ7DATsg4fuUDjQtsYLmWjnk2obnNHgV3vE2Y4jejSOJVBQ==", + "license": "MIT", + "dependencies": { + "mimic-fn": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/dedent": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.2.tgz", + "integrity": "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==", + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/default-browser": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.1.tgz", + "integrity": "sha512-m1pAzaJgZ/gssEqlOhJkPJp8Xly7QyW6xcrkUa2KKcDeDSEMP7X8xipU3snUcfisTQx0w1AGae+9UtJSfVnXGw==", + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", + "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", + "license": "MIT" + }, + "node_modules/diff": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.4.tgz", + "integrity": "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dot-prop": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz", + "integrity": "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==", + "license": "MIT", + "dependencies": { + "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/dotenv": { + "version": "17.4.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", + "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.427", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.427.tgz", + "integrity": "sha512-n14zb3FdsChZ2BNobqNHAJMcP3ifFv4paox2LvCrfVAQcqGiSURgbJl+PfMpHVCNFkStnNc+RRVtPBTVW5PDgw==", + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.24.5", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.5.tgz", + "integrity": "sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/enquirer": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", + "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", + "license": "MIT", + "dependencies": { + "ansi-colors": "^4.1.1", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz", + "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.2", + "@esbuild/android-arm": "0.28.2", + "@esbuild/android-arm64": "0.28.2", + "@esbuild/android-x64": "0.28.2", + "@esbuild/darwin-arm64": "0.28.2", + "@esbuild/darwin-x64": "0.28.2", + "@esbuild/freebsd-arm64": "0.28.2", + "@esbuild/freebsd-x64": "0.28.2", + "@esbuild/linux-arm": "0.28.2", + "@esbuild/linux-arm64": "0.28.2", + "@esbuild/linux-ia32": "0.28.2", + "@esbuild/linux-loong64": "0.28.2", + "@esbuild/linux-mips64el": "0.28.2", + "@esbuild/linux-ppc64": "0.28.2", + "@esbuild/linux-riscv64": "0.28.2", + "@esbuild/linux-s390x": "0.28.2", + "@esbuild/linux-x64": "0.28.2", + "@esbuild/netbsd-arm64": "0.28.2", + "@esbuild/netbsd-x64": "0.28.2", + "@esbuild/openbsd-arm64": "0.28.2", + "@esbuild/openbsd-x64": "0.28.2", + "@esbuild/openharmony-arm64": "0.28.2", + "@esbuild/sunos-x64": "0.28.2", + "@esbuild/win32-arm64": "0.28.2", + "@esbuild/win32-ia32": "0.28.2", + "@esbuild/win32-x64": "0.28.2" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true, + "license": "MIT" + }, + "node_modules/eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "license": "MIT", + "dependencies": { + "eventsource-parser": "^3.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/eventsource-parser": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.1.1.tgz", + "integrity": "sha512-EKN1vKAMcZ8MlYMpaNuxN6R9yakzH6uajHcHVTqWJzvu5pWw9DyhbP35HH8MVBQ+dZjAfDxk+A8NiR9KWaXiyQ==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/execa": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.1.tgz", + "integrity": "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==", + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^4.0.0", + "cross-spawn": "^7.0.6", + "figures": "^6.1.0", + "get-stream": "^9.0.0", + "human-signals": "^8.0.1", + "is-plain-obj": "^4.1.0", + "is-stream": "^4.0.1", + "npm-run-path": "^6.0.0", + "pretty-ms": "^9.2.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^4.0.0", + "yoctocolors": "^2.1.1" + }, + "engines": { + "node": "^18.19.0 || >=20.5.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "license": "MIT", + "peer": true, + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.7.0.tgz", + "integrity": "sha512-hOwV7WOxXfjRpAM1DSJWZDXx3GhplwD8IfwuwvogD8i1Qnkgosw/H45s4ZnFAUHDAhPjlY9hLBvJhKmGMyY26g==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "ip-address": "^10.2.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-uri": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.7.tgz", + "integrity": "sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fastq": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.3.tgz", + "integrity": "sha512-XKv5nnLs6nLF71NgiKJLIZFLkPyIEuOselLG7ujZnGrRfQK8HpvY+WqKhAJUAdLomwVHErVS4LfxFlPq0/FTAw==", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/figures": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", + "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", + "license": "MIT", + "dependencies": { + "is-unicode-supported": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "license": "MIT", + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fs-extra": { + "version": "11.4.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.4.0.tgz", + "integrity": "sha512-EQsFzMUJkCKGr1ePqlYADkIUmHW1s3ZXr5Yqy6wbGrfUCphpl2maM/kyOIRA2HpP3AaFQTZXD4ldjek+nccddA==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/fuzzysort": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fuzzysort/-/fuzzysort-3.1.0.tgz", + "integrity": "sha512-sR9BNCjBg6LNgwvxlBd0sBABvQitkLzoVY9MYYROQVX/FvfJ4Mai9LsGhDgd8qYdds0bY77VzYd5iuB+v5rwQQ==", + "license": "MIT" + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/get-own-enumerable-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-own-enumerable-keys/-/get-own-enumerable-keys-1.0.0.tgz", + "integrity": "sha512-PKsK2FSrQCyxcGHsGrLDcK0lx+0Ke+6e8KFFozA9/fIQLhQzPaRvJFdcz7+Axg3jUH/Mq+NI4xa5u/UT2tQskA==", + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", + "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", + "license": "MIT", + "dependencies": { + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hono": { + "version": "4.13.7", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.13.7.tgz", + "integrity": "sha512-c8/gF9ac8Y78/agExVocyLevgR+JlpNB444Py0FSX8pJoPdYUfUzRcXtYEYGwt6l19qIlVZPN5Mfsw9jFShmQQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=16.9.0" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/human-signals": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", + "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ip-address": { + "version": "10.7.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.7.0.tgz", + "integrity": "sha512-BGFsyJd5mpXp3rK6jIdADLNgpJUK1jnjzvYF8lK+VyDab9JAmqN0YOKDdP17HlgKb2+ehPgDc8EtnRLbGCAMhA==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "license": "MIT" + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-in-ssh": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-in-ssh/-/is-in-ssh-1.0.0.tgz", + "integrity": "sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-interactive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", + "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/is-regexp": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-3.1.0.tgz", + "integrity": "sha512-rbku49cWloU5bSMI+zaRaXdQHXnthP6DZ/vLnfdSKyL4zUzuWnomtOEiZZOd+ioQ+avFo/qau3KPTc7Fjy1uPA==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-wsl": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz", + "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/isomorphic.js": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/isomorphic.js/-/isomorphic.js-0.2.5.tgz", + "integrity": "sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==", + "license": "MIT", + "funding": { + "type": "GitHub Sponsors ❤", + "url": "https://github.com/sponsors/dmonad" + } + }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/jose": { + "version": "6.2.12", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.12.tgz", + "integrity": "sha512-9NiFmJEex0sy2Dk58j2UGBSHgUs2ypF9eZSu4L6vjOX3Dp96Sw1F3uL+H+D1sx02jZZdzUT0HgvCy59CuvXcWw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/json-schema-typed": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", + "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", + "license": "BSD-2-Clause" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/lib0": { + "version": "0.2.117", + "resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.117.tgz", + "integrity": "sha512-DeXj9X5xDCjgKLU/7RR+/HQEVzuuEUiwldwOGsHK/sfAfELGWEyTcf0x+uOvCvK3O2zPmZePXWL85vtia6GyZw==", + "license": "MIT", + "dependencies": { + "isomorphic.js": "^0.2.4" + }, + "bin": { + "0ecdsa-generate-keypair": "bin/0ecdsa-generate-keypair.js", + "0gentesthtml": "bin/gentesthtml.js", + "0serve": "bin/0serve.js" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "type": "GitHub Sponsors ❤", + "url": "https://github.com/sponsors/dmonad" + } + }, + "node_modules/lightningcss": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" + }, + "node_modules/linkifyjs": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/linkifyjs/-/linkifyjs-4.3.3.tgz", + "integrity": "sha512-P8aEP5U/D1/IlTY2OeYsErdwh9bGuLE30NcXtKEjgdHcahveQoQwM2yZNsioQHsWFz0P7KKudisbrzCgR0sDHg==", + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "license": "MIT", + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/log-symbols": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-6.0.0.tgz", + "integrity": "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==", + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "is-unicode-supported": "^1.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols/node_modules/is-unicode-supported": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", + "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lucide-react": { + "version": "1.45.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.45.0.tgz", + "integrity": "sha512-yH1ubCAduho9UR7oJhRXIQXogksRILBiTuZC4/bQIGeB9JOkxMlSuEHyyZpo1Z3S0yWJO2KTSUZbjiNvVxeOUw==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.1.tgz", + "integrity": "sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/mimic-fn": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-3.1.0.tgz", + "integrity": "sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.18", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", + "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/negotiator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.1.0.tgz", + "integrity": "sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg==", + "license": "MIT", + "dependencies": { + "content-type": "^2.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/negotiator/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/next": { + "version": "16.3.5", + "resolved": "https://registry.npmjs.org/next/-/next-16.3.5.tgz", + "integrity": "sha512-MdtsTgzyfCPRLC6uJ1mN8ao7lyJ4BB0U6Inhnx3gta1UcCIdHK3yxLG0E8OWQteWD8/Q0qb8A5o7wJaL8M9y2w==", + "license": "MIT", + "dependencies": { + "@next/env": "16.3.5", + "@swc/helpers": "0.5.23", + "baseline-browser-mapping": "^2.9.19", + "caniuse-lite": "^1.0.30001579", + "postcss": "8.5.23", + "styled-jsx": "5.1.6" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": ">=20.9.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "16.3.5", + "@next/swc-darwin-x64": "16.3.5", + "@next/swc-linux-arm64-gnu": "16.3.5", + "@next/swc-linux-arm64-musl": "16.3.5", + "@next/swc-linux-x64-gnu": "16.3.5", + "@next/swc-linux-x64-musl": "16.3.5", + "@next/swc-win32-arm64-msvc": "16.3.5", + "@next/swc-win32-x64-msvc": "16.3.5", + "sharp": "^0.35.4" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "@playwright/test": "^1.51.1", + "babel-plugin-react-compiler": "*", + "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@playwright/test": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, + "node_modules/next/node_modules/postcss": { + "version": "8.5.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.23.tgz", + "integrity": "sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.16", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/node-releases": { + "version": "2.0.55", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.55.tgz", + "integrity": "sha512-mIrE/Cw9y+9Au6dS5vDKDhQza9YvG6w+ZrS6X+ZzA7yFW/soAeaups4Qzn1bL6g5FVy8WtP79+0j82oPIbqRjQ==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/npm-run-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", + "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0", + "unicorn-magic": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-treeify": { + "version": "1.1.33", + "resolved": "https://registry.npmjs.org/object-treeify/-/object-treeify-1.1.33.tgz", + "integrity": "sha512-EFVjAYfzWqWsBMRHPMAXLCDIJnpMhdWAqR7xG6M6a2cs6PMFpl/+Z20w9zDW4vkxOFfddegBKq9Rehd0bxWE7A==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/onetime/node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/open": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/open/-/open-11.0.3.tgz", + "integrity": "sha512-b3souUgU0VaAvMmzA4+9cRgrOgMdE0vc9H9VhQ0FYnjjcyZ8R/f9DiwMU9V8rl+vjf05On9UJhbgb06fsTs87Q==", + "license": "MIT", + "dependencies": { + "default-browser": "^5.5.1", + "define-lazy-prop": "^3.0.0", + "is-in-ssh": "^1.0.0", + "is-inside-container": "^1.0.0", + "powershell-utils": "^0.2.1", + "wsl-utils": "^1.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-8.2.0.tgz", + "integrity": "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==", + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "cli-cursor": "^5.0.0", + "cli-spinners": "^2.9.2", + "is-interactive": "^2.0.0", + "is-unicode-supported": "^2.0.0", + "log-symbols": "^6.0.0", + "stdin-discarder": "^0.2.2", + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/ansi-regex": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.3.0.tgz", + "integrity": "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ora/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/orderedmap": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/orderedmap/-/orderedmap-2.1.1.tgz", + "integrity": "sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==", + "license": "MIT" + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "license": "MIT", + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-ms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", + "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "license": "MIT" + }, + "node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-to-regexp": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/phoenix": { + "version": "1.8.13", + "resolved": "https://registry.npmjs.org/phoenix/-/phoenix-1.8.13.tgz", + "integrity": "sha512-5UByH7ejdsPLd6eihwzV3L3i6YX4mHUtFjLaE8S1YuA+3aBj37rzH6KDjxBuPlB04o9ZHVz+PKRiUg3aYYvzmQ==", + "license": "MIT", + "peer": true + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkce-challenge": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", + "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", + "license": "MIT", + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/pkg-up": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz", + "integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==", + "license": "MIT", + "dependencies": { + "find-up": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/playwright": { + "version": "1.63.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.63.0.tgz", + "integrity": "sha512-+7ziBLidS4NaNCdt57SUDT+wYmmd5fmiQejUic/kb+YsYSCPyOOE9sebzMjNmQrsnNpDJqd4WHvV/8lfKfUDUg==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.63.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/playwright-core": { + "version": "1.63.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.63.0.tgz", + "integrity": "sha512-rYCsBF/M5HjUch52bbtVONEFjv6Xu8sm8h72dNlR5bzIE1fvC/bxgspzkjSfU+MweEMmPM8KJebG6nnyxo5mCg==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/postcss": { + "version": "8.5.28", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz", + "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.18", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz", + "integrity": "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/powershell-utils": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/powershell-utils/-/powershell-utils-0.2.1.tgz", + "integrity": "sha512-C+y9x90UElAddDZmV4qOx9W53B61PO7cIqWz2dQsWlwswuq4mr8NEwytdGKboYbQlGZ3awrkTeNvcZiZNHnQ8A==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pretty-ms": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.3.1.tgz", + "integrity": "sha512-HzMy3Geq23nVALD/M2LliU+F+M+gVNsvkQWWqeBZ8HDiCgzo6YPJ/Omrmtq24EFrIsk0a3EkQGEd7bDOo+IhGA==", + "license": "MIT", + "dependencies": { + "parse-ms": "^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/prompts/node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/prosemirror-changeset": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prosemirror-changeset/-/prosemirror-changeset-2.4.2.tgz", + "integrity": "sha512-ViYrjMSg3YFiXwIhKaluu+/mi3Yrxt6AR8ri14ulTaGcZtXO1CThl7A2gv79qx5fQnOw8woKwyBU2u+9PVCm3w==", + "license": "MIT", + "dependencies": { + "prosemirror-transform": "^1.0.0" + } + }, + "node_modules/prosemirror-commands": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.7.2.tgz", + "integrity": "sha512-q6Q6szxqdu9Xd6EcdKsqXghu5nQdZTpB4Q9yd04WRc7/jt763e/rT60Owh0L1GYY+T46o5rD+9lEN36dZS43tw==", + "license": "MIT", + "dependencies": { + "prosemirror-model": "^1.0.0", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.10.2" + } + }, + "node_modules/prosemirror-dropcursor": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/prosemirror-dropcursor/-/prosemirror-dropcursor-1.8.3.tgz", + "integrity": "sha512-FoYbsJR8gK+DGlqhNoE29Loa38eIZPzQRIb1VMaDNBoo4OLP6vVof/jR8qFY/6XvUd6Dhug8MDCHl2a/h8RTfQ==", + "license": "MIT", + "dependencies": { + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.1.0", + "prosemirror-view": "^1.1.0" + } + }, + "node_modules/prosemirror-gapcursor": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/prosemirror-gapcursor/-/prosemirror-gapcursor-1.4.1.tgz", + "integrity": "sha512-pMdYaEnjNMSwl11yjEGtgTmLkR08m/Vl+Jj443167p9eB3HVQKhYCc4gmHVDsLPODfZfjr/MmirsdyZziXbQKw==", + "license": "MIT", + "dependencies": { + "prosemirror-keymap": "^1.0.0", + "prosemirror-model": "^1.0.0", + "prosemirror-state": "^1.0.0", + "prosemirror-view": "^1.0.0" + } + }, + "node_modules/prosemirror-history": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/prosemirror-history/-/prosemirror-history-1.5.0.tgz", + "integrity": "sha512-zlzTiH01eKA55UAf1MEjtssJeHnGxO0j4K4Dpx+gnmX9n+SHNlDqI2oO1Kv1iPN5B1dm5fsljCfqKF9nFL6HRg==", + "license": "MIT", + "dependencies": { + "prosemirror-state": "^1.2.2", + "prosemirror-transform": "^1.0.0", + "prosemirror-view": "^1.31.0", + "rope-sequence": "^1.3.0" + } + }, + "node_modules/prosemirror-inputrules": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/prosemirror-inputrules/-/prosemirror-inputrules-1.5.1.tgz", + "integrity": "sha512-7wj4uMjKaXWAQ1CDgxNzNtR9AlsuwzHfdFH1ygEHA2KHF2DOEaXl1CJfNPAKCg9qNEh4rum975QLaCiQPyY6Fw==", + "license": "MIT", + "dependencies": { + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.0.0" + } + }, + "node_modules/prosemirror-keymap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/prosemirror-keymap/-/prosemirror-keymap-1.2.3.tgz", + "integrity": "sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw==", + "license": "MIT", + "dependencies": { + "prosemirror-state": "^1.0.0", + "w3c-keyname": "^2.2.0" + } + }, + "node_modules/prosemirror-model": { + "version": "1.25.11", + "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.25.11.tgz", + "integrity": "sha512-QWg9RhnpLlogAmp3p96uEFrE5txQpFynd4vhBAELkwgOCWQs/X0yCzB3/hrHqiPwf91RG5KyWq6553zs9JqIOQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "orderedmap": "^2.0.0" + } + }, + "node_modules/prosemirror-schema-list": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/prosemirror-schema-list/-/prosemirror-schema-list-1.5.1.tgz", + "integrity": "sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q==", + "license": "MIT", + "dependencies": { + "prosemirror-model": "^1.0.0", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.7.3" + } + }, + "node_modules/prosemirror-state": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.4.tgz", + "integrity": "sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw==", + "license": "MIT", + "peer": true, + "dependencies": { + "prosemirror-model": "^1.0.0", + "prosemirror-transform": "^1.0.0", + "prosemirror-view": "^1.27.0" + } + }, + "node_modules/prosemirror-tables": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/prosemirror-tables/-/prosemirror-tables-1.8.5.tgz", + "integrity": "sha512-V/0cDCsHKHe/tfWkeCmthNUcEp1IVO3p6vwN8XtwE9PZQLAZJigbw3QoraAdfJPir4NKJtNvOB8oYGKRl+t0Dw==", + "license": "MIT", + "dependencies": { + "prosemirror-keymap": "^1.2.3", + "prosemirror-model": "^1.25.4", + "prosemirror-state": "^1.4.4", + "prosemirror-transform": "^1.10.5", + "prosemirror-view": "^1.41.4" + } + }, + "node_modules/prosemirror-transform": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.12.1.tgz", + "integrity": "sha512-t4F5615FycnCqsX7ShTUs8+jfnwcf46kuFRvSl/3qFx2QTZ4DjgowesLHQXcFP7G//TjesqZG3WtgL7vdyVJyA==", + "license": "MIT", + "dependencies": { + "prosemirror-model": "^1.21.0" + } + }, + "node_modules/prosemirror-view": { + "version": "1.42.3", + "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.42.3.tgz", + "integrity": "sha512-oTN7EtH+CpwxU9NrwEYWd0UZ4JUx7l048l5A2Xppm4p/60isZYLnth9QVQmC3VRIvdrIWCxwZSd+Uz791G31/w==", + "license": "MIT", + "peer": true, + "dependencies": { + "prosemirror-model": "^1.25.8", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.1.0" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.16.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz", + "integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==", + "license": "BSD-3-Clause", + "dependencies": { + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/radix-ui": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/radix-ui/-/radix-ui-1.6.7.tgz", + "integrity": "sha512-QBdhh1arIEUvPC0dQ5+nwWAxt7+N+oP/9jPwjJkGFoSk/sqxg32gJtSXGtFh8frAIcS6oC9cx2Q+7KYCQLOAeA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-accessible-icon": "1.1.15", + "@radix-ui/react-accordion": "1.2.20", + "@radix-ui/react-alert-dialog": "1.1.23", + "@radix-ui/react-arrow": "1.1.15", + "@radix-ui/react-aspect-ratio": "1.1.15", + "@radix-ui/react-avatar": "1.2.6", + "@radix-ui/react-checkbox": "1.3.11", + "@radix-ui/react-collapsible": "1.1.20", + "@radix-ui/react-collection": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-context-menu": "2.3.7", + "@radix-ui/react-dialog": "1.1.23", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-dismissable-layer": "1.1.19", + "@radix-ui/react-dropdown-menu": "2.1.24", + "@radix-ui/react-focus-guards": "1.1.6", + "@radix-ui/react-focus-scope": "1.1.16", + "@radix-ui/react-form": "0.1.16", + "@radix-ui/react-hover-card": "1.1.23", + "@radix-ui/react-label": "2.1.15", + "@radix-ui/react-menu": "2.1.24", + "@radix-ui/react-menubar": "1.1.24", + "@radix-ui/react-navigation-menu": "1.2.22", + "@radix-ui/react-one-time-password-field": "0.1.16", + "@radix-ui/react-password-toggle-field": "0.1.11", + "@radix-ui/react-popover": "1.1.23", + "@radix-ui/react-popper": "1.3.7", + "@radix-ui/react-portal": "1.1.17", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-progress": "1.1.16", + "@radix-ui/react-radio-group": "1.4.7", + "@radix-ui/react-roving-focus": "1.1.19", + "@radix-ui/react-scroll-area": "1.2.18", + "@radix-ui/react-select": "2.3.7", + "@radix-ui/react-separator": "1.1.15", + "@radix-ui/react-slider": "1.4.7", + "@radix-ui/react-slot": "1.3.3", + "@radix-ui/react-switch": "1.3.7", + "@radix-ui/react-tabs": "1.1.21", + "@radix-ui/react-toast": "1.2.23", + "@radix-ui/react-toggle": "1.1.18", + "@radix-ui/react-toggle-group": "1.1.19", + "@radix-ui/react-toolbar": "1.1.19", + "@radix-ui/react-tooltip": "1.2.16", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-effect-event": "0.0.5", + "@radix-ui/react-use-escape-keydown": "1.1.5", + "@radix-ui/react-use-is-hydrated": "0.1.3", + "@radix-ui/react-use-layout-effect": "1.1.4", + "@radix-ui/react-use-size": "1.1.4", + "@radix-ui/react-visually-hidden": "1.2.11" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/range-parser": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz", + "integrity": "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/react": { + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.3.0.tgz", + "integrity": "sha512-E8LUcbtBWt20bbl2YoHfx4ZDBdxVTfOKtCZn9cDSJ4l6/nuoApcpIBcj47t2wZoVX8g2ZHuMHbiShgCR1T5Sog==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.3.0.tgz", + "integrity": "sha512-JDk8dgif51OjFoDE70+OT9ICyYr+69HlmihNwp1+Nsfbna3t5sIiCa9ZJktDmQ4/1b/rn26hIAR2uYXDMr5r0Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "scheduler": "^0.28.0" + }, + "peerDependencies": { + "react": "^19.3.0" + } + }, + "node_modules/react-remove-scroll": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz", + "integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==", + "license": "MIT", + "dependencies": { + "react-remove-scroll-bar": "^2.3.7", + "react-style-singleton": "^2.2.3", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.3", + "use-sidecar": "^1.1.3" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll-bar": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", + "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", + "license": "MIT", + "dependencies": { + "react-style-singleton": "^2.2.2", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-style-singleton": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", + "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", + "license": "MIT", + "dependencies": { + "get-nonce": "^1.0.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/recast": { + "version": "0.23.21", + "resolved": "https://registry.npmjs.org/recast/-/recast-0.23.21.tgz", + "integrity": "sha512-mFAyJq9vUbSTARLZUvAEf1z3YxlvAwswbmxMx2mPA/MSm4KmpwvwvhsH/NIrZhyOuwD60Lzyw2qh83uCbgTPYw==", + "license": "MIT", + "dependencies": { + "ast-types": "^0.16.1", + "esprima": "~4.0.0", + "source-map": "~0.6.1", + "tiny-invariant": "^1.3.3", + "tslib": "^2.0.1" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "license": "MIT", + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/restore-cursor/node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rolldown": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.8.tgz", + "integrity": "sha512-Z67nTmhZe7anqnM/EjI392w5i/ANUinjip7QYsOyN37oayduxt3ksdX0hf5OOamkAd53BiIHfbfSzfUmzKFQqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.149.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm-eabi": "1.2.8", + "@rolldown/binding-android-arm64": "1.2.8", + "@rolldown/binding-darwin-arm64": "1.2.8", + "@rolldown/binding-darwin-x64": "1.2.8", + "@rolldown/binding-freebsd-x64": "1.2.8", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.8", + "@rolldown/binding-linux-arm64-gnu": "1.2.8", + "@rolldown/binding-linux-arm64-musl": "1.2.8", + "@rolldown/binding-linux-ppc64-gnu": "1.2.8", + "@rolldown/binding-linux-s390x-gnu": "1.2.8", + "@rolldown/binding-linux-x64-gnu": "1.2.8", + "@rolldown/binding-linux-x64-musl": "1.2.8", + "@rolldown/binding-openharmony-arm64": "1.2.8", + "@rolldown/binding-win32-arm64-msvc": "1.2.8", + "@rolldown/binding-win32-x64-msvc": "1.2.8" + } + }, + "node_modules/rope-sequence": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/rope-sequence/-/rope-sequence-1.3.4.tgz", + "integrity": "sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==", + "license": "MIT" + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/run-applescript": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/scheduler": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.28.0.tgz", + "integrity": "sha512-juorfCmIkIw8tT+p5BXSm6PJjQF/ycEYmKyzURCIt/RaZIhL+PulbQ9Yu2z1HdOJDdqDTlxA1+xKBmHXJsczAw==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/shadcn": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/shadcn/-/shadcn-4.21.0.tgz", + "integrity": "sha512-UU2mFNusW8C5rvadKdH69vERYZqUlOOlXBcf0MYhYLdTGP6DPti7X4qovCu+RTfCqsAgq/T+YfE0Vnttxh9aiw==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/parser": "^7.28.0", + "@babel/plugin-transform-typescript": "^7.28.0", + "@babel/preset-typescript": "^7.27.1", + "@dotenvx/dotenvx": "^1.48.4", + "@modelcontextprotocol/sdk": "^1.26.0", + "@types/validate-npm-package-name": "^4.0.2", + "browserslist": "^4.26.2", + "cn": "^0.2.4", + "commander": "^14.0.0", + "cosmiconfig": "^9.0.0", + "dedent": "^1.6.0", + "deepmerge": "^4.3.1", + "diff": "^8.0.2", + "execa": "^9.6.0", + "fast-glob": "^3.3.3", + "fs-extra": "^11.3.1", + "fuzzysort": "^3.1.0", + "kleur": "^4.1.5", + "open": "^11.0.0", + "ora": "^8.2.0", + "postcss": "^8.5.6", + "postcss-selector-parser": "^7.1.0", + "prompts": "^2.4.2", + "recast": "^0.23.11", + "socks": "^2.8.8", + "stringify-object": "^5.0.0", + "ts-morph": "^26.0.0", + "tsconfig-paths": "^4.2.0", + "undici": "^7.27.2", + "validate-npm-package-name": "^7.0.1", + "zod": "^3.24.1", + "zod-to-json-schema": "^3.24.6" + }, + "bin": { + "shadcn": "dist/index.js" + }, + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/shadcn/node_modules/cn": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/cn/-/cn-0.2.6.tgz", + "integrity": "sha512-+i4L0zUGgRcEnhsxueVrP7iBGxBx5iD0WOTYg1MwFEu2ZyCmH5Ov2V1cul2Ht5UchRQQgftCf4be/RxspuW6QQ==", + "license": "MIT", + "bin": { + "cn": "bin/cn.mjs" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/sharp": { + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.4.tgz", + "integrity": "sha512-n++8XWcj+jCOr2IOl7h8LbKnGBDY4aPbmprMONBNFdn0ImXqpGVv5zliDs0V9HbmbCQLpbuo2ej9rAoOQTvMDA==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/colour": "^1.1.0", + "detect-libc": "^2.1.2", + "semver": "^7.8.5" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.35.4", + "@img/sharp-darwin-x64": "0.35.4", + "@img/sharp-freebsd-wasm32": "0.35.4", + "@img/sharp-libvips-darwin-arm64": "1.3.3", + "@img/sharp-libvips-darwin-x64": "1.3.3", + "@img/sharp-libvips-linux-arm": "1.3.3", + "@img/sharp-libvips-linux-arm64": "1.3.3", + "@img/sharp-libvips-linux-ppc64": "1.3.3", + "@img/sharp-libvips-linux-riscv64": "1.3.3", + "@img/sharp-libvips-linux-s390x": "1.3.3", + "@img/sharp-libvips-linux-x64": "1.3.3", + "@img/sharp-libvips-linuxmusl-arm64": "1.3.3", + "@img/sharp-libvips-linuxmusl-x64": "1.3.3", + "@img/sharp-linux-arm": "0.35.4", + "@img/sharp-linux-arm64": "0.35.4", + "@img/sharp-linux-ppc64": "0.35.4", + "@img/sharp-linux-riscv64": "0.35.4", + "@img/sharp-linux-s390x": "0.35.4", + "@img/sharp-linux-x64": "0.35.4", + "@img/sharp-linuxmusl-arm64": "0.35.4", + "@img/sharp-linuxmusl-x64": "0.35.4", + "@img/sharp-webcontainers-wasm32": "0.35.4", + "@img/sharp-win32-arm64": "0.35.4", + "@img/sharp-win32-ia32": "0.35.4", + "@img/sharp-win32-x64": "0.35.4" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", - "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, "engines": { - "node": ">= 12.0.0" + "node": ">= 0.4" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/linkifyjs": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/linkifyjs/-/linkifyjs-4.3.3.tgz", - "integrity": "sha512-P8aEP5U/D1/IlTY2OeYsErdwh9bGuLE30NcXtKEjgdHcahveQoQwM2yZNsioQHsWFz0P7KKudisbrzCgR0sDHg==", + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", "license": "MIT" }, - "node_modules/nanoid": { - "version": "3.3.18", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", - "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.10", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.10.tgz", + "integrity": "sha512-e0VyvkVTwVYViNovRkZ9aodhxVlyoMn7eJhVUPxZ+eK9P/7CBkxvvsBOHqFPEH416726W8tLXXXjKwqgTErrCQ==", + "license": "MIT", + "dependencies": { + "ip-address": "^10.1.1", + "smart-buffer": "^4.2.0" }, "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + "node": ">= 10.0.0", + "npm": ">= 3.0.0" } }, - "node_modules/orderedmap": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/orderedmap/-/orderedmap-2.1.1.tgz", - "integrity": "sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==", - "license": "MIT" + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } }, - "node_modules/phoenix": { - "version": "1.8.13", - "resolved": "https://registry.npmjs.org/phoenix/-/phoenix-1.8.13.tgz", - "integrity": "sha512-5UByH7ejdsPLd6eihwzV3L3i6YX4mHUtFjLaE8S1YuA+3aBj37rzH6KDjxBuPlB04o9ZHVz+PKRiUg3aYYvzmQ==", - "license": "MIT", - "peer": true + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } }, - "node_modules/picomatch": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", - "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", - "dev": true, + "node_modules/stdin-discarder": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz", + "integrity": "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==", "license": "MIT", - "peer": true, "engines": { - "node": ">=12" + "node": ">=18" }, "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/playwright": { - "version": "1.63.0", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.63.0.tgz", - "integrity": "sha512-+7ziBLidS4NaNCdt57SUDT+wYmmd5fmiQejUic/kb+YsYSCPyOOE9sebzMjNmQrsnNpDJqd4WHvV/8lfKfUDUg==", - "dev": true, - "license": "Apache-2.0", + "node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "license": "MIT", "dependencies": { - "playwright-core": "1.63.0" - }, - "bin": { - "playwright": "cli.js" + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=20" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/playwright-core": { - "version": "1.63.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.63.0.tgz", - "integrity": "sha512-rYCsBF/M5HjUch52bbtVONEFjv6Xu8sm8h72dNlR5bzIE1fvC/bxgspzkjSfU+MweEMmPM8KJebG6nnyxo5mCg==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "playwright-core": "cli.js" - }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.3.0.tgz", + "integrity": "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==", + "license": "MIT", "engines": { - "node": ">=20" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/postcss": { - "version": "8.5.28", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz", - "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], + "node_modules/string-width/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "license": "MIT", "dependencies": { - "nanoid": "^3.3.18", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" + "ansi-regex": "^6.2.2" }, "engines": { - "node": "^10 || ^12 || >=14" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/prosemirror-changeset": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prosemirror-changeset/-/prosemirror-changeset-2.4.2.tgz", - "integrity": "sha512-ViYrjMSg3YFiXwIhKaluu+/mi3Yrxt6AR8ri14ulTaGcZtXO1CThl7A2gv79qx5fQnOw8woKwyBU2u+9PVCm3w==", - "license": "MIT", + "node_modules/stringify-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-5.0.0.tgz", + "integrity": "sha512-zaJYxz2FtcMb4f+g60KsRNFOpVMUyuJgA51Zi5Z1DOTC3S59+OQiVOzE9GZt0x72uBGWKsQIuBKeF9iusmKFsg==", + "license": "BSD-2-Clause", "dependencies": { - "prosemirror-transform": "^1.0.0" + "get-own-enumerable-keys": "^1.0.0", + "is-obj": "^3.0.0", + "is-regexp": "^3.1.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/yeoman/stringify-object?sponsor=1" } }, - "node_modules/prosemirror-commands": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.7.2.tgz", - "integrity": "sha512-q6Q6szxqdu9Xd6EcdKsqXghu5nQdZTpB4Q9yd04WRc7/jt763e/rT60Owh0L1GYY+T46o5rD+9lEN36dZS43tw==", + "node_modules/stringify-object/node_modules/is-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-3.0.0.tgz", + "integrity": "sha512-IlsXEHOjtKhpN8r/tRFj2nDyTmHvcfNeu/nrRIcXE17ROeatXchkojffa1SpdqW4cr/Fj6QkEf/Gn4zf6KKvEQ==", "license": "MIT", - "dependencies": { - "prosemirror-model": "^1.0.0", - "prosemirror-state": "^1.0.0", - "prosemirror-transform": "^1.10.2" + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/prosemirror-dropcursor": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/prosemirror-dropcursor/-/prosemirror-dropcursor-1.8.3.tgz", - "integrity": "sha512-FoYbsJR8gK+DGlqhNoE29Loa38eIZPzQRIb1VMaDNBoo4OLP6vVof/jR8qFY/6XvUd6Dhug8MDCHl2a/h8RTfQ==", + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "license": "MIT", "dependencies": { - "prosemirror-state": "^1.0.0", - "prosemirror-transform": "^1.1.0", - "prosemirror-view": "^1.1.0" + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" } }, - "node_modules/prosemirror-gapcursor": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/prosemirror-gapcursor/-/prosemirror-gapcursor-1.4.1.tgz", - "integrity": "sha512-pMdYaEnjNMSwl11yjEGtgTmLkR08m/Vl+Jj443167p9eB3HVQKhYCc4gmHVDsLPODfZfjr/MmirsdyZziXbQKw==", + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", "license": "MIT", - "dependencies": { - "prosemirror-keymap": "^1.0.0", - "prosemirror-model": "^1.0.0", - "prosemirror-state": "^1.0.0", - "prosemirror-view": "^1.0.0" + "engines": { + "node": ">=4" } }, - "node_modules/prosemirror-history": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/prosemirror-history/-/prosemirror-history-1.5.0.tgz", - "integrity": "sha512-zlzTiH01eKA55UAf1MEjtssJeHnGxO0j4K4Dpx+gnmX9n+SHNlDqI2oO1Kv1iPN5B1dm5fsljCfqKF9nFL6HRg==", + "node_modules/strip-final-newline": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", + "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", "license": "MIT", - "dependencies": { - "prosemirror-state": "^1.2.2", - "prosemirror-transform": "^1.0.0", - "prosemirror-view": "^1.31.0", - "rope-sequence": "^1.3.0" + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/prosemirror-inputrules": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/prosemirror-inputrules/-/prosemirror-inputrules-1.5.1.tgz", - "integrity": "sha512-7wj4uMjKaXWAQ1CDgxNzNtR9AlsuwzHfdFH1ygEHA2KHF2DOEaXl1CJfNPAKCg9qNEh4rum975QLaCiQPyY6Fw==", + "node_modules/style-mod": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.3.tgz", + "integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==", + "license": "MIT" + }, + "node_modules/styled-jsx": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", + "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", "license": "MIT", "dependencies": { - "prosemirror-state": "^1.0.0", - "prosemirror-transform": "^1.0.0" + "client-only": "0.0.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "peerDependencies": { + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } } }, - "node_modules/prosemirror-keymap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/prosemirror-keymap/-/prosemirror-keymap-1.2.3.tgz", - "integrity": "sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw==", + "node_modules/synixir-collaboration-example": { + "resolved": "examples/collaboration", + "link": true + }, + "node_modules/systeminformation": { + "version": "5.33.10", + "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.33.10.tgz", + "integrity": "sha512-/NXbMVASt2UbSVgmWto4aBTIAeuYY7zOELpZybmNxqpsrbo5uYHpuEPf5nkyrPng0uG5YOb+Yv6s0FyKY4mDQg==", "license": "MIT", - "dependencies": { - "prosemirror-state": "^1.0.0", - "w3c-keyname": "^2.2.0" + "os": [ + "darwin", + "linux", + "win32", + "freebsd", + "openbsd", + "netbsd", + "sunos", + "android" + ], + "bin": { + "systeminformation": "lib/cli.js" + }, + "engines": { + "node": ">=10.0.0" + }, + "funding": { + "type": "Buy me a coffee", + "url": "https://www.buymeacoffee.com/systeminfo" } }, - "node_modules/prosemirror-model": { - "version": "1.25.11", - "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.25.11.tgz", - "integrity": "sha512-QWg9RhnpLlogAmp3p96uEFrE5txQpFynd4vhBAELkwgOCWQs/X0yCzB3/hrHqiPwf91RG5KyWq6553zs9JqIOQ==", + "node_modules/tailwindcss": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.3.tgz", + "integrity": "sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==", + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", "license": "MIT", - "peer": true, - "dependencies": { - "orderedmap": "^2.0.0" + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, - "node_modules/prosemirror-schema-list": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/prosemirror-schema-list/-/prosemirror-schema-list-1.5.1.tgz", - "integrity": "sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q==", + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, "license": "MIT", "dependencies": { - "prosemirror-model": "^1.0.0", - "prosemirror-state": "^1.0.0", - "prosemirror-transform": "^1.7.3" + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/prosemirror-state": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.4.tgz", - "integrity": "sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw==", + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "license": "MIT", - "peer": true, "dependencies": { - "prosemirror-model": "^1.0.0", - "prosemirror-transform": "^1.0.0", - "prosemirror-view": "^1.27.0" + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" } }, - "node_modules/prosemirror-tables": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/prosemirror-tables/-/prosemirror-tables-1.8.5.tgz", - "integrity": "sha512-V/0cDCsHKHe/tfWkeCmthNUcEp1IVO3p6vwN8XtwE9PZQLAZJigbw3QoraAdfJPir4NKJtNvOB8oYGKRl+t0Dw==", + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", "license": "MIT", - "dependencies": { - "prosemirror-keymap": "^1.2.3", - "prosemirror-model": "^1.25.4", - "prosemirror-state": "^1.4.4", - "prosemirror-transform": "^1.10.5", - "prosemirror-view": "^1.41.4" + "engines": { + "node": ">=0.6" } }, - "node_modules/prosemirror-transform": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.12.1.tgz", - "integrity": "sha512-t4F5615FycnCqsX7ShTUs8+jfnwcf46kuFRvSl/3qFx2QTZ4DjgowesLHQXcFP7G//TjesqZG3WtgL7vdyVJyA==", + "node_modules/ts-morph": { + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-26.0.0.tgz", + "integrity": "sha512-ztMO++owQnz8c/gIENcM9XfCEzgoGphTv+nKpYNM1bgsdOVC/jRZuEBf6N+mLLDNg68Kl+GgUZfOySaRiG1/Ug==", "license": "MIT", "dependencies": { - "prosemirror-model": "^1.21.0" + "@ts-morph/common": "~0.27.0", + "code-block-writer": "^13.0.3" } }, - "node_modules/prosemirror-view": { - "version": "1.42.3", - "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.42.3.tgz", - "integrity": "sha512-oTN7EtH+CpwxU9NrwEYWd0UZ4JUx7l048l5A2Xppm4p/60isZYLnth9QVQmC3VRIvdrIWCxwZSd+Uz791G31/w==", + "node_modules/tsconfig-paths": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", + "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", "license": "MIT", - "peer": true, "dependencies": { - "prosemirror-model": "^1.25.8", - "prosemirror-state": "^1.0.0", - "prosemirror-transform": "^1.1.0" + "json5": "^2.2.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=6" } }, - "node_modules/rolldown": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.8.tgz", - "integrity": "sha512-Z67nTmhZe7anqnM/EjI392w5i/ANUinjip7QYsOyN37oayduxt3ksdX0hf5OOamkAd53BiIHfbfSzfUmzKFQqQ==", + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/tsx": { + "version": "4.23.13", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.13.tgz", + "integrity": "sha512-BL5MGkRln6aDYhb0xbQlEAGw743BaZYWdbWtdJOBriYJboKgUUYCadFp2/FpBBZquBC/ezNBn7wMMPx7FDZUDw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "@oxc-project/types": "=0.149.0", - "@rolldown/pluginutils": "^1.0.0" + "esbuild": "~0.28.0" }, "bin": { - "rolldown": "bin/cli.mjs" + "tsx": "dist/cli.mjs" }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=18.0.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm-eabi": "1.2.8", - "@rolldown/binding-android-arm64": "1.2.8", - "@rolldown/binding-darwin-arm64": "1.2.8", - "@rolldown/binding-darwin-x64": "1.2.8", - "@rolldown/binding-freebsd-x64": "1.2.8", - "@rolldown/binding-linux-arm-gnueabihf": "1.2.8", - "@rolldown/binding-linux-arm64-gnu": "1.2.8", - "@rolldown/binding-linux-arm64-musl": "1.2.8", - "@rolldown/binding-linux-ppc64-gnu": "1.2.8", - "@rolldown/binding-linux-s390x-gnu": "1.2.8", - "@rolldown/binding-linux-x64-gnu": "1.2.8", - "@rolldown/binding-linux-x64-musl": "1.2.8", - "@rolldown/binding-openharmony-arm64": "1.2.8", - "@rolldown/binding-win32-arm64-msvc": "1.2.8", - "@rolldown/binding-win32-x64-msvc": "1.2.8" + "fsevents": "~2.3.3" } }, - "node_modules/rope-sequence": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/rope-sequence/-/rope-sequence-1.3.4.tgz", - "integrity": "sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==", - "license": "MIT" - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" + "node_modules/tw-animate-css": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/tw-animate-css/-/tw-animate-css-1.4.0.tgz", + "integrity": "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Wombosvideo" } }, - "node_modules/style-mod": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.3.tgz", - "integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==", - "license": "MIT" - }, - "node_modules/synixir-collaboration-example": { - "resolved": "examples/collaboration", - "link": true - }, - "node_modules/tinyglobby": { - "version": "0.2.17", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", - "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", - "dev": true, + "node_modules/type-is": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", + "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", "license": "MIT", "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.4" + "content-type": "^2.0.0", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" }, "engines": { - "node": ">=12.0.0" + "node": ">= 18" }, "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/type-is/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/typescript": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-7.0.2.tgz", "integrity": "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc" }, @@ -1872,6 +9373,149 @@ "@typescript/typescript-win32-x64": "7.0.2" } }, + "node_modules/undici": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.29.1.tgz", + "integrity": "sha512-RYONW2MeafgYlkVOKYKkA/Ag7BmXqgIWCa8t1m0JcxrQg9pI9lEqRhAOruOBCbAohOa/gkCF+iPi9hrgvTzu6Q==", + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.3.tgz", + "integrity": "sha512-pJ2sYawQS0R/WI928Gj5GlPhTGzbMelq0+4INtSYNDV9ErKJcX6xjGWkoG/VnB3dpUm00zALaqkrUD77pO5TDQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/use-callback-ref": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", + "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sidecar": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", + "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", + "license": "MIT", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/validate-npm-package-name": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-7.0.2.tgz", + "integrity": "sha512-hVDIBwsRruT73PbK7uP5ebUt+ezEtCmzZz3F59BSr2F6OVFnJ/6h8liuvdLrQ88Xmnk6/+xGGuq+pG9WwTuy3A==", + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/vite": { "version": "8.2.2", "resolved": "https://registry.npmjs.org/vite/-/vite-8.2.2.tgz", @@ -1956,6 +9600,55 @@ "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", "license": "MIT" }, + "node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/wsl-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-1.0.0.tgz", + "integrity": "sha512-Hl0ZOAs672vg+06kfujwRhoS6/jehvULrlFkuF2dRu6pHgA8U06h3xqNIqNNU1LTXPcedxByAR4GS6pwQK0mgA==", + "license": "MIT", + "dependencies": { + "is-wsl": "^3.1.0", + "powershell-utils": "^0.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/wsl-utils/node_modules/powershell-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/powershell-utils/-/powershell-utils-0.1.0.tgz", + "integrity": "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/y-codemirror.next": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/y-codemirror.next/-/y-codemirror.next-0.3.6.tgz", @@ -2009,6 +9702,12 @@ "yjs": "^13.0.0" } }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "license": "ISC" + }, "node_modules/yjs": { "version": "13.6.32", "resolved": "https://registry.npmjs.org/yjs/-/yjs-13.6.32.tgz", @@ -2027,6 +9726,52 @@ "url": "https://github.com/sponsors/dmonad" } }, + "node_modules/yocto-spinner": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/yocto-spinner/-/yocto-spinner-1.2.2.tgz", + "integrity": "sha512-DODGl1wJjA/s5pnJFKau9lIYHT81lnhob1i3e1TjxZRxEhWRKl74nTbWE6H5KlkViQQTo/Z29YFdxzTZAMY3ng==", + "license": "MIT", + "dependencies": { + "yoctocolors": "^2.1.1" + }, + "engines": { + "node": ">=18.19" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yoctocolors": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.2.0.tgz", + "integrity": "sha512-xYqdZFUK/VYazNl/oCDYN+3WloWQwMfZxBoiNt6qNyk+xfOdi598muWE42rNZFp1kNOiqW936q5RhUdnpqElSg==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.25.2", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", + "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.25.28 || ^4" + } + }, "packages/client": { "name": "@synixir/client", "version": "0.1.0", diff --git a/package.json b/package.json index a92f5c5..7dfa086 100644 --- a/package.json +++ b/package.json @@ -2,13 +2,27 @@ "name": "synixir", "private": true, "type": "module", - "workspaces": ["packages/client", "examples/collaboration"], - "engines": { "node": ">=22.12.0" }, + "workspaces": [ + "packages/client", + "examples/collaboration" + ], + "engines": { + "node": ">=22.12.0" + }, "scripts": { - "test": "npm test --workspace @synixir/client && node --test examples/collaboration/kanban/model.test.js examples/collaboration/whiteboard/model.test.js examples/collaboration/flowchart/model.test.js examples/collaboration/table/model.test.js examples/collaboration/multiplayer-form/model.test.js", - "test:package": "node scripts/check-client-package.js", - "test:browser": "npm test --workspace synixir-collaboration-example", - "build": "npm run build --workspace synixir-collaboration-example" + "test": "npm test --workspace @synixir/client && node --import tsx --test examples/collaboration/kanban/model.test.ts examples/collaboration/whiteboard/model.test.ts examples/collaboration/flowchart/model.test.ts examples/collaboration/table/model.test.ts examples/collaboration/multiplayer-form/model.test.ts", + "test:package": "node --import tsx scripts/check-client-package.ts", + "test:browser": "npm test --workspace synixir-collaboration-example --", + "build": "npm run build --workspace @synixir/client && npm run build --workspace synixir-collaboration-example", + "typecheck": "npm run build --workspace @synixir/client && npm run typecheck --workspaces --if-present && tsc -p scripts/tsconfig.json" }, - "devDependencies": { "typescript": "7.0.2" } + "devDependencies": { + "@types/http-proxy": "1.17.17", + "@types/node": "22.20.2", + "@types/phoenix": "1.6.7", + "http-proxy": "1.18.1", + "tsx": "4.23.13", + "typescript": "7.0.2", + "vite": "8.2.2" + } } diff --git a/packages/client/README.md b/packages/client/README.md index 7245ce0..b81f772 100644 --- a/packages/client/README.md +++ b/packages/client/README.md @@ -12,7 +12,9 @@ tarball in your application along with `yjs@^13.6.32`: npm install /path/to/synixir-client-0.1.0.tgz yjs@^13.6.32 ``` -Use a browser ESM bundler such as Vite. TypeScript declarations are included. +The SDK is written in strict TypeScript and ships compiled ESM JavaScript with +generated declarations. Use it from TypeScript or JavaScript with Next.js, Vite, +or another browser ESM bundler. Importing the module is safe without browser globals; connecting requires browser WebSocket/fetch APIs. Outside the browser, supply `serverUrl` when constructing a room. Yjs is a shared peer dependency, so applications and editor bindings use @@ -22,7 +24,7 @@ one copy. Phoenix and the channel provider are private implementation details. Sign in and create or join a room through your application's account UI first. The default access callback uses Synixir's cookie session API. Serve `/api` and -`/socket` through the frontend origin, as the repository's Vite example does. +`/socket` through the frontend origin, as the repository's Next.js example does. ```js import { SynixirRoom } from "@synixir/client"; diff --git a/packages/client/package.json b/packages/client/package.json index 4c1dff1..9af69e1 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -3,17 +3,34 @@ "version": "0.1.0", "description": "Synixir room synchronization, awareness, durable save status, and recovery for Yjs applications", "type": "module", - "types": "./index.d.ts", + "types": "./dist/index.d.ts", "exports": { - ".": { "types": "./index.d.ts", "import": "./src/index.js", "default": "./src/index.js" } + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "default": "./dist/index.js" + } + }, + "files": [ + "dist", + "README.md" + ], + "scripts": { + "build": "tsc -p tsconfig.json", + "typecheck": "tsc -p tsconfig.json --noEmit && tsc -p test/tsconfig.json", + "prepack": "npm run build", + "test": "node --import tsx --test test/*.test.ts", + "pretest": "npm run build" }, - "files": ["src", "index.d.ts", "README.md"], - "scripts": { "test": "node --test test/*.test.js" }, "dependencies": { "phoenix": "1.8.13", "y-phoenix-channel": "0.3.0", "y-protocols": "1.0.7" }, - "peerDependencies": { "yjs": "^13.6.32" }, - "devDependencies": { "yjs": "13.6.32" } + "peerDependencies": { + "yjs": "^13.6.32" + }, + "devDependencies": { + "yjs": "13.6.32" + } } diff --git a/packages/client/src/access.js b/packages/client/src/access.ts similarity index 66% rename from packages/client/src/access.js rename to packages/client/src/access.ts index f86095b..3b2bce7 100644 --- a/packages/client/src/access.js +++ b/packages/client/src/access.ts @@ -1,5 +1,8 @@ +import type { RoomOptions } from "./types.js"; export class SynixirError extends Error { - constructor(code, { retryable = false } = {}) { + readonly code: string; + readonly retryable: boolean; + constructor(code: string, { retryable = false }: { retryable?: boolean } = {}) { super(code); this.name = "SynixirError"; this.code = code; @@ -7,14 +10,14 @@ export class SynixirError extends Error { } } -export function normalizeError(error) { +export function normalizeError(error: unknown) { if (error instanceof SynixirError) return error; - return new SynixirError(error?.name === "TimeoutError" ? "timeout" : "network_error", { retryable: true }); + return new SynixirError((error instanceof Error ? error.name : undefined) === "TimeoutError" ? "timeout" : "network_error", { retryable: true }); } -export function sessionAccess(serverUrl, userId) { +export function sessionAccess(serverUrl: URL, userId: string): NonNullable { return async ({ roomId, signal }) => { - async function request(path, options = {}) { + async function request(path: string, options: RequestInit = {}) { const response = await fetch(new URL(path, serverUrl), { credentials: "same-origin", signal, ...options, }); diff --git a/packages/client/src/chunked-transport.js b/packages/client/src/chunked-transport.ts similarity index 76% rename from packages/client/src/chunked-transport.js rename to packages/client/src/chunked-transport.ts index edd9e85..767686f 100644 --- a/packages/client/src/chunked-transport.js +++ b/packages/client/src/chunked-transport.ts @@ -1,18 +1,30 @@ +import type { Channel, Push, Socket } from "phoenix"; +declare module "phoenix" { + interface Channel { + trigger(event: string, payload: object): void; + } +} +type Reply = { reason?: string; saved?: boolean }; +type Outcome = { status: string; value: Reply }; +interface Receipt { receive(status: string, callback: (value: Reply) => void): Receipt; finish(status: string, value: Reply): void } +interface TransferLimits { max_transfer_bytes: number; chunk_bytes: number; transfer_timeout_ms: number; max_message_bytes: number } +interface Incoming { id: number; total: number; offset: number; data: Uint8Array; timer: ReturnType } +interface Queued { event: string; buffer: ArrayBuffer; timeout: number; result: Receipt } // Keeps the provider's Yjs protocol and Push.receive API intact. Only complete // messages reach Yjs; partial uploads are never reported as durable saves. const HEADER = 13; const EVENTS = ["yjs", "yjs_sync", "save_update"]; function receipt() { - let outcome; - const callbacks = []; + let outcome: Outcome | undefined; + const callbacks: {status: string; callback: (value: Reply) => void}[] = []; const result = { - receive(status, callback) { + receive(status: string, callback: (value: Reply) => void) { callbacks.push({ status, callback }); if (outcome?.status === status) callback(outcome.value); return result; }, - finish(status, value) { + finish(status: string, value: Reply) { if (outcome) return; outcome = { status, value }; for (const listener of callbacks) { @@ -23,20 +35,20 @@ function receipt() { return result; } -export function chunkedSocket(socket, writable) { - const cleanups = []; +export function chunkedSocket(socket: Socket, writable: boolean) { + const cleanups: (() => void)[] = []; return { endPointURL: () => socket.endPointURL(), - channel: (topic, params) => adaptChannel(socket.channel(topic, { ...params, chunked_sync: 1 }), writable, cleanups), + channel: (topic: string, params: object) => adaptChannel(socket.channel(topic, { ...params, chunked_sync: 1 }), writable, cleanups), destroy: () => cleanups.splice(0).forEach(cleanup => cleanup()), }; } -function adaptChannel(channel, writable, cleanups) { +function adaptChannel(channel: Channel, writable: boolean, cleanups: (() => void)[]) { const push = channel.push.bind(channel); const on = channel.on.bind(channel); const off = channel.off.bind(channel); - const bindings = new Map(); + const bindings = new Map(); let closed = false; channel.on = (event, callback) => { const ref = on(event, callback); @@ -51,15 +63,15 @@ function adaptChannel(channel, writable, cleanups) { } return off(event, ref); }; - let limits; + let limits: TransferLimits | undefined; let generation = 0; let nextId = 0; - let queue = []; + let queue: Queued[] = []; let queuedBytes = 0; let running = false; - let incoming; - const pending = new Set(); - const requests = new Set(); + let incoming: Incoming | undefined; + const pending = new Set(); + const requests = new Set<(reply: Outcome) => void>(); function clearIncoming() { clearTimeout(incoming?.timer); @@ -93,7 +105,7 @@ function adaptChannel(channel, writable, cleanups) { limits = transfer; }); - function syncError(reason) { + function syncError(reason: string | undefined) { clearIncoming(); channel.trigger("sync_error", { reason }); } @@ -127,9 +139,9 @@ function adaptChannel(channel, writable, cleanups) { } }); - function request(event, buffer, timeout) { + function request(event: string, buffer: ArrayBuffer, timeout: number): Promise { return new Promise(resolve => { - const finish = reply => { requests.delete(finish); resolve(reply); }; + const finish = (reply: Outcome) => { requests.delete(finish); resolve(reply); }; requests.add(finish); push(event, buffer, timeout) .receive("ok", value => finish({ status: "ok", value })) @@ -139,15 +151,15 @@ function adaptChannel(channel, writable, cleanups) { } async function drain() { - if (running) return; + if (running || !limits) return; running = true; const current = generation; while (queue.length && current === generation) { - const { event, buffer, timeout, result } = queue.shift(); + const { event, buffer, timeout, result } = queue.shift()!; const id = nextId = (nextId + 1) >>> 0; const bytes = new Uint8Array(buffer); const deadline = Date.now() + limits.transfer_timeout_ms; - let reply; + let reply: Outcome = {status: "error", value: {reason: "empty_transfer"}}; for (let offset = 0; offset < bytes.length; offset += limits.chunk_bytes) { if (current !== generation) return; if (Date.now() >= deadline) { @@ -183,7 +195,7 @@ function adaptChannel(channel, writable, cleanups) { if (closed || (!writable && writes)) { const result = receipt(); result.finish("error", { reason: closed ? "disconnected" : "read_only" }); - return result; + return result as unknown as Push; } if (!EVENTS.includes(event) || !(buffer instanceof ArrayBuffer) || !limits || buffer.byteLength <= limits.max_message_bytes) { @@ -196,7 +208,7 @@ function adaptChannel(channel, writable, cleanups) { if (current === generation) syncError("timeout"); }); } - return result; + return result as unknown as Push; } const result = receipt(); if (event === "yjs_sync") { @@ -210,17 +222,17 @@ function adaptChannel(channel, writable, cleanups) { if (buffer.byteLength > limits.max_transfer_bytes || queuedBytes + buffer.byteLength > 2 * limits.max_transfer_bytes) { result.finish("error", { reason: "message_too_large" }); - return result; + return result as unknown as Push; } if (channel.state !== "joined") { result.finish("error", { reason: "disconnected" }); - return result; + return result as unknown as Push; } queuedBytes += buffer.byteLength; pending.add(result); queue.push({ event, buffer, timeout, result }); void drain(); - return result; + return result as unknown as Push; }; return channel; } diff --git a/packages/client/src/index.js b/packages/client/src/index.ts similarity index 81% rename from packages/client/src/index.js rename to packages/client/src/index.ts index 63ebec0..b5f77d3 100644 --- a/packages/client/src/index.js +++ b/packages/client/src/index.ts @@ -1,3 +1,5 @@ +import type { RoomOptions, RoomState } from "./types.js"; +export type * from "./types.js"; import { Socket } from "phoenix"; import { PhoenixChannelProvider } from "y-phoenix-channel"; import { Awareness, removeAwarenessStates } from "y-protocols/awareness"; @@ -10,29 +12,29 @@ export { SynixirError }; /** Owns one account's local document, awareness, and recoverable room connection. */ export class SynixirRoom { - #doc; - #awareness; - #roomId; - #userId; - #serverUrl; - #getAccess; - #listeners = new Set(); - #state = Object.freeze({ connection: "disconnected", role: null, readOnly: true, + #doc: Y.Doc; + #awareness: Awareness; + #roomId: string; + #userId: string; + #serverUrl: URL; + #getAccess: NonNullable; + #listeners = new Set<(state: RoomState) => void>(); + #state: RoomState = Object.freeze({ connection: "disconnected", role: null, readOnly: true, saveStatus: "not-saved", hasUnsavedChanges: false, error: null, saveError: null }); - #saves; + #saves: ReturnType; #generation = 0; - #controller; - #timer; - #retryTimer; + #controller?: AbortController; + #timer?: ReturnType; + #retryTimer?: ReturnType; #retries = 0; #wanted = false; #frozen = false; #destroyed = false; - #pending; - #active; + #pending?: { promise: Promise; resolve: () => void; reject: (error: SynixirError) => void }; + #active?: {socket: Socket; provider: PhoenixChannelProvider; transport: ReturnType}; #socketClosed = Promise.resolve(); - constructor({ roomId, userId, serverUrl = globalThis.location?.origin, getAccess } = {}) { + constructor({ roomId, userId, serverUrl = globalThis.location?.origin, getAccess }: RoomOptions) { if (typeof roomId !== "string" || !/^[a-zA-Z0-9][a-zA-Z0-9_-]{0,127}$/.test(roomId)) { throw new TypeError("roomId must contain 1–128 ASCII letters, digits, underscores or hyphens and start with a letter or digit"); } @@ -55,14 +57,15 @@ export class SynixirRoom { get awareness() { return this.#awareness; } get state() { return this.#state; } - subscribe(listener) { + /** Immediately receives the current immutable state. Returns an unsubscribe function. */ + subscribe(listener: (state: RoomState) => void) { if (typeof listener !== "function") throw new TypeError("listener must be a function"); if (!this.#destroyed) this.#listeners.add(listener); this.#notify(listener); - return () => this.#listeners.delete(listener); + return () => { this.#listeners.delete(listener); }; } - #notify(listener) { + #notify(listener: (state: RoomState) => void) { try { listener(this.#state); } catch (error) { // A rendering error must not stop cleanup, saving, or other subscribers. @@ -71,7 +74,7 @@ export class SynixirRoom { } } - #publish(changes) { + #publish(changes: Partial) { const state = { ...this.#state, ...changes }; state.readOnly = this.#destroyed || this.#frozen || !state.role || state.role === "viewer"; if (state.role === "viewer") { @@ -79,11 +82,12 @@ export class SynixirRoom { state.hasUnsavedChanges = false; state.saveError = null; } - if (Object.keys(state).every(key => state[key] === this.#state[key])) return; + if ((Object.keys(state) as (keyof RoomState)[]).every(key => state[key] === this.#state[key])) return; this.#state = Object.freeze(state); for (const listener of this.#listeners) this.#notify(listener); } + /** Resolves on synchronization, separately from durable save confirmation. */ connect() { if (this.#destroyed) return Promise.reject(new SynixirError("destroyed")); if (this.#frozen) return Promise.reject(this.#state.error); @@ -93,8 +97,8 @@ export class SynixirRoom { clearTimeout(this.#retryTimer); const generation = ++this.#generation; const controller = this.#controller = new AbortController(); - let resolve, reject; - const promise = new Promise((yes, no) => { resolve = yes; reject = no; }); + let resolve!: () => void, reject!: (error: SynixirError) => void; + const promise = new Promise((yes, no) => { resolve = yes; reject = no; }); this.#pending = { promise, resolve, reject }; this.#publish({ connection: this.#retries ? "reconnecting" : "connecting", error: null }); if (generation !== this.#generation || this.#destroyed) return promise; @@ -105,7 +109,7 @@ export class SynixirRoom { return promise; } - async #start(generation, signal) { + async #start(generation: number, signal: AbortSignal) { const current = () => generation === this.#generation && !this.#destroyed; try { const access = await this.#getAccess({ roomId: this.#roomId, signal }); @@ -121,7 +125,7 @@ export class SynixirRoom { const writable = access.role !== "viewer"; const socket = new Socket(new URL("/socket", this.#serverUrl).href); const transport = chunkedSocket(socket, writable); - const provider = new PhoenixChannelProvider(transport, `document:${this.#roomId}`, this.#doc, { + const provider = new PhoenixChannelProvider(transport as unknown as Socket, `document:${this.#roomId}`, this.#doc, { connect: false, disableBc: true, awareness: this.#awareness, params: { token: access.token }, }); this.#active = { socket, provider, transport }; @@ -146,7 +150,7 @@ export class SynixirRoom { socket.onClose(() => this.#fail(generation, new SynixirError("disconnected", { retryable: true }))); socket.connect(); provider.connect(); - const channel = provider.channel; + const channel = provider.channel!; channel.on("access_revoked", () => this.#fail(generation, new SynixirError("access_changed"))); channel.on("sync_error", ({ reason }) => this.#fail(generation, new SynixirError(reason ?? "sync_failed"))); channel.joinPush.receive("error", ({ reason }) => this.#fail(generation, new SynixirError(reason ?? "room_unavailable"))); @@ -156,14 +160,14 @@ export class SynixirRoom { } } - #settle(error) { + #settle(error?: SynixirError) { const pending = this.#pending; this.#pending = undefined; if (error) pending?.reject(error); else pending?.resolve(); } - #stop(error) { + #stop(error: SynixirError) { this.#generation++; clearTimeout(this.#timer); clearTimeout(this.#retryTimer); @@ -176,14 +180,14 @@ export class SynixirRoom { if (active) { active.provider.destroy(); active.transport.destroy(); - this.#socketClosed = this.#socketClosed.then(() => new Promise(resolve => active.socket.disconnect(resolve))); + this.#socketClosed = this.#socketClosed.then(() => new Promise(resolve => active.socket.disconnect(resolve))); } removeAwarenessStates(this.#awareness, [...this.#awareness.getStates().keys()].filter(id => id !== this.#doc.clientID), this); return this.#socketClosed; } - #fail(generation, error) { + #fail(generation: number, error: SynixirError) { if (generation !== this.#generation || this.#destroyed) return; this.#frozen = ["access_changed", "account_changed"].includes(error.code); const retry = this.#wanted && error.retryable && !this.#frozen; @@ -197,6 +201,7 @@ export class SynixirRoom { } } + /** Cancels pending work and waits for socket shutdown; retains the document. */ disconnect() { if (this.#destroyed) return this.#socketClosed; this.#wanted = false; @@ -206,6 +211,7 @@ export class SynixirRoom { return closed; } + /** Terminal and idempotent. Destroys the owned document and awareness. */ destroy() { if (this.#destroyed) return this.#socketClosed; this.#destroyed = true; diff --git a/packages/client/src/save-status.js b/packages/client/src/save-status.ts similarity index 78% rename from packages/client/src/save-status.js rename to packages/client/src/save-status.ts index 6832d3b..1d5bd6a 100644 --- a/packages/client/src/save-status.js +++ b/packages/client/src/save-status.ts @@ -1,17 +1,20 @@ +import type { PhoenixChannelProvider } from "y-phoenix-channel"; +import type { RoomState } from "./types.js"; +type SaveState = Pick; import * as Y from "yjs"; // A sync handshake is not a database acknowledgement. Keep revisions across // transports, but accept save replies only from the currently attached one. -export function trackSaveStatus(doc, render) { - let provider; +export function trackSaveStatus(doc: Y.Doc, render: (state: SaveState) => void) { + let provider: PhoenixChannelProvider | undefined; let writable = false; let revision = 0; let confirmed = -1; let generation = 0; - let failureReason = null; - const acknowledged = new Set(); + let failureReason: string | null = null; + const acknowledged = new Set(); - function state() { + function state(): SaveState { const online = provider?.synced && provider.channel?.state === "joined"; const saveStatus = confirmed >= revision ? "saved" : failureReason ? "failed" @@ -23,7 +26,7 @@ export function trackSaveStatus(doc, render) { function show() { render(state()); } - function save(update, capturedRevision, fullState = false) { + function save(update: Uint8Array, capturedRevision: number, fullState = false) { const channel = provider?.channel; if (!writable || !provider?.synced || channel?.state !== "joined") return show(); const capturedGeneration = generation; @@ -50,14 +53,14 @@ export function trackSaveStatus(doc, render) { show(); } - function updated(update, origin) { + function updated(update: Uint8Array, origin: unknown) { if (!provider || origin !== provider) { revision++; save(update, revision); } } - function synced(value) { + function synced(value: boolean) { generation++; acknowledged.clear(); failureReason = null; @@ -76,7 +79,7 @@ export function trackSaveStatus(doc, render) { doc.on("update", updated); return { get state() { return state(); }, - attach(next, canWrite) { + attach(next: PhoenixChannelProvider, canWrite: boolean) { detach(); provider = next; writable = canWrite; diff --git a/packages/client/index.d.ts b/packages/client/src/types.ts similarity index 56% rename from packages/client/index.d.ts rename to packages/client/src/types.ts index 5673a13..5e2808b 100644 --- a/packages/client/index.d.ts +++ b/packages/client/src/types.ts @@ -1,5 +1,4 @@ -import type { Doc } from "yjs"; -import type { Awareness } from "y-protocols/awareness"; +import type { SynixirError } from "./access.js"; export type RoomRole = "owner" | "editor" | "viewer"; export interface RoomAccess { @@ -32,22 +31,3 @@ export interface RoomState { readonly error: SynixirError | null; readonly saveError: string | null; } -export class SynixirError extends Error { - readonly code: string; - readonly retryable: boolean; - constructor(code: string, options?: { retryable?: boolean }); -} -export class SynixirRoom { - constructor(options: RoomOptions); - readonly doc: Doc; - readonly awareness: Awareness; - readonly state: RoomState; - /** Immediately receives the current immutable state. Returns an unsubscribe function. */ - subscribe(listener: (state: RoomState) => void): () => void; - /** Resolves on synchronization, separately from durable save confirmation. */ - connect(): Promise; - /** Cancels pending work and waits for socket shutdown; retains the document. */ - disconnect(): Promise; - /** Terminal and idempotent. Destroys the owned document and awareness. */ - destroy(): Promise; -} diff --git a/packages/client/test/lifecycle.test.js b/packages/client/test/lifecycle.test.ts similarity index 92% rename from packages/client/test/lifecycle.test.js rename to packages/client/test/lifecycle.test.ts index 286791a..79dbda4 100644 --- a/packages/client/test/lifecycle.test.js +++ b/packages/client/test/lifecycle.test.ts @@ -1,6 +1,6 @@ import { test } from "node:test"; import assert from "node:assert/strict"; -import { SynixirRoom, SynixirError } from "@synixir/client"; +import { SynixirRoom, SynixirError, type RoomState, type RoomAccess, type AccessRequest, type RoomOptions } from "@synixir/client"; import * as Y from "yjs"; const options = { roomId: "sdk-unit", userId: "account-id", serverUrl: "http://127.0.0.1:4010" }; @@ -9,16 +9,16 @@ const tick = () => new Promise(resolve => setImmediate(resolve)); // These run through the package entry point without browser globals or a server. test("validates options before allocating a document or awareness timer", () => { for (const roomId of ["", "bad/id", "_invalid", "x".repeat(129), 123]) { - assert.throws(() => new SynixirRoom({ ...options, roomId }), TypeError); + assert.throws(() => new SynixirRoom({ ...options, roomId } as RoomOptions), TypeError); } assert.throws(() => new SynixirRoom({ ...options, userId: "" }), TypeError); assert.throws(() => new SynixirRoom({ ...options, serverUrl: "https://example.com/prefix" }), TypeError); - assert.throws(() => new SynixirRoom({ ...options, getAccess: true }), TypeError); + assert.throws(() => new SynixirRoom({ ...options, getAccess: true as unknown as RoomOptions["getAccess"] }), TypeError); }); test("owns one shared Yjs document and exposes immutable snapshots with unsubscribe", async () => { const room = new SynixirRoom(options); - const states = []; + const states: RoomState[] = []; const unsubscribe = room.subscribe(state => states.push(state)); try { assert.ok(room.doc instanceof Y.Doc); @@ -39,7 +39,7 @@ test("owns one shared Yjs document and exposes immutable snapshots with unsubscr }); test("coalesces connects, aborts pending access, and ignores a late old result", async () => { - const requests = []; + const requests: (AccessRequest & { resolve: (access: RoomAccess) => void })[] = []; const room = new SynixirRoom({ ...options, getAccess: request => new Promise(resolve => { requests.push({ ...request, resolve }); }) }); @@ -90,7 +90,7 @@ test("nonretryable access errors permit explicit retry and reject malformed gran let calls = 0; const room = new SynixirRoom({ ...options, getAccess: async () => { if (++calls === 1) throw new SynixirError("unauthorized"); - return { token: "grant", userId: options.userId, role: "admin" }; + return { token: "grant", userId: options.userId, role: "admin" as RoomAccess["role"] }; } }); try { await assert.rejects(room.connect(), { code: "unauthorized" }); @@ -106,7 +106,7 @@ test("destroy cancels scheduled recovery and subscriptions stop after final stat calls++; throw new SynixirError("offline", { retryable: true }); } }); - const states = []; + const states: RoomState["connection"][] = []; room.subscribe(state => states.push(state.connection)); await assert.rejects(room.connect(), { code: "offline" }); assert.equal(room.state.connection, "reconnecting"); @@ -137,7 +137,7 @@ test("a subscriber can cancel a new attempt before any access request starts", a test("access timeout settles a callback that ignores cancellation", async t => { t.mock.timers.enable({ apis: ["setTimeout"] }); - let signal; + let signal!: AbortSignal; const room = new SynixirRoom({ ...options, getAccess: request => { signal = request.signal; return new Promise(() => {}); diff --git a/packages/client/test/tsconfig.json b/packages/client/test/tsconfig.json new file mode 100644 index 0000000..8596a3f --- /dev/null +++ b/packages/client/test/tsconfig.json @@ -0,0 +1,8 @@ +{ + "compilerOptions": { + "target": "ES2022", "lib": ["ES2024", "DOM"], "types": ["node"], + "module": "NodeNext", "moduleResolution": "NodeNext", + "strict": true, "noEmit": true, "skipLibCheck": true + }, + "include": ["*.ts"] +} diff --git a/packages/client/tsconfig.json b/packages/client/tsconfig.json new file mode 100644 index 0000000..974220c --- /dev/null +++ b/packages/client/tsconfig.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "target": "ES2022", "module": "NodeNext", "moduleResolution": "NodeNext", + "lib": ["ES2022", "DOM"], "strict": true, "skipLibCheck": true, + "declaration": true, "declarationMap": true, "sourceMap": true, + "rootDir": "src", "outDir": "dist" + }, + "include": ["src/**/*.ts"] +} diff --git a/scripts/check-client-package.js b/scripts/check-client-package.ts similarity index 80% rename from scripts/check-client-package.js rename to scripts/check-client-package.ts index c44b5cc..0d8d24c 100644 --- a/scripts/check-client-package.js +++ b/scripts/check-client-package.ts @@ -7,19 +7,19 @@ import { fileURLToPath } from "node:url"; const root = fileURLToPath(new URL("..", import.meta.url)); const temporary = mkdtempSync(join(tmpdir(), "synixir-client-consumer-")); -const run = (command, args, cwd = temporary) => execFileSync(command, args, { +const run = (command: string, args: string[], cwd = temporary) => execFileSync(command, args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], }); try { const [pack] = JSON.parse(run("npm", ["pack", "--workspace", "@synixir/client", "--json", "--pack-destination", temporary], root)); - assert.ok(pack.files.some(file => file.path === "index.d.ts")); - assert.ok(pack.files.some(file => file.path === "README.md")); - assert.ok(pack.files.every(file => /^(src\/|index\.d\.ts$|README\.md$|package\.json$)/.test(file.path))); + assert.ok(pack.files.some((file: { path: string; }) => file.path === "dist/index.d.ts")); + assert.ok(pack.files.some((file: { path: string; }) => file.path === "README.md")); + assert.ok(pack.files.every((file: { path: string; }) => /^(dist\/|README\.md$|package\.json$)/.test(file.path))); const example = JSON.parse(readFileSync(join(root, "examples/collaboration/package.json"), "utf8")); const workspace = JSON.parse(readFileSync(join(root, "package.json"), "utf8")); writeFileSync(join(temporary, "package.json"), JSON.stringify({ private: true, type: "module", dependencies: { "@synixir/client": `file:./${pack.filename}`, yjs: example.dependencies.yjs, - typescript: workspace.devDependencies.typescript, vite: example.devDependencies.vite, + typescript: workspace.devDependencies.typescript, vite: workspace.devDependencies.vite, } })); run("npm", ["install", "--ignore-scripts", "--no-audit", "--no-fund"]); writeFileSync(join(temporary, "consumer.ts"), readFileSync(join(root, "packages/client/test/consumer.ts"))); @@ -48,8 +48,8 @@ try { run("node", ["node_modules/vite/bin/vite.js", "build"]); console.log(`Verified ${pack.filename}: packed files, ESM import, shared Yjs, NodeNext/bundler types, and Vite build.`); } catch (error) { - if (error.stdout) process.stderr.write(error.stdout); - if (error.stderr) process.stderr.write(error.stderr); + if (error instanceof Error && "stdout" in error && error.stdout) process.stderr.write(String(error.stdout)); + if (error instanceof Error && "stderr" in error && error.stderr) process.stderr.write(String(error.stderr)); throw error; } finally { rmSync(temporary, { recursive: true, force: true }); diff --git a/scripts/operations/load.js b/scripts/operations/load.ts similarity index 89% rename from scripts/operations/load.js rename to scripts/operations/load.ts index a004453..be7cd97 100644 --- a/scripts/operations/load.js +++ b/scripts/operations/load.ts @@ -12,17 +12,18 @@ const exitListeners = process.listenerCount("exit"); const listenerLimit = process.getMaxListeners(); process.setMaxListeners(listenerLimit + count + 2); const origin = `http://127.0.0.1:${port}`; -const clients = []; -const groups = Array.from({ length: roomCount }, () => []); -const saveMs = [], convergeMs = [], connectMs = []; +interface Client { room: SynixirRoom; api: ReturnType; user: { id: string }; roomId: string; index: number } +const clients: Client[] = []; +const groups: Client[][] = Array.from({ length: roomCount }, () => []); +const saveMs: number[] = [], convergeMs: number[] = [], connectMs: number[] = []; let updateBytes = 0; const cpuStart = process.cpuUsage(); const started = performance.now(); function account() { - const cookies = new Map(); - let csrf; - return async (path, method = "GET", body, signal = AbortSignal.timeout(10000)) => { + const cookies = new Map(); + let csrf: string | undefined; + return async (path: string|URL, method = "GET", body?: Record, signal = AbortSignal.timeout(10000)) => { const response = await fetch(new URL(path, origin), { method, signal, headers: { "Content-Type": "application/json", Cookie: [...cookies].map(([k, v]) => `${k}=${v}`).join("; "), ...(csrf ? { "x-csrf-token": csrf } : {}) }, @@ -39,7 +40,7 @@ function account() { }; } -async function until(predicate, label, timeout = 45000) { +async function until(predicate: () => boolean, label: string, timeout = 45000) { const deadline = performance.now() + timeout; while (!predicate()) { for (const { room } of clients) { @@ -56,19 +57,19 @@ async function metrics() { headers: { Authorization: `Bearer ${process.env.SYNIXIR_METRICS_TOKEN}` }, signal: AbortSignal.timeout(5000) }); assert.equal(response.status, 200); const text = await response.text(); - const value = name => Number(text.match(new RegExp(`^${name} (.+)$`, "m"))?.[1] ?? 0); + const value = (name: string) => Number(text.match(new RegExp(`^${name} (.+)$`, "m"))?.[1] ?? 0); return { text, memory_bytes: value("synixir_vm_memory_bytes"), run_queue: value("synixir_vm_run_queue"), active_channels: value("synixir_channels_active"), active_documents: value("synixir_documents_active"), database_queue_seconds_sum: value("synixir_database_queue_seconds_sum"), database_queue_count: value("synixir_database_queue_seconds_count") }; } -function percentiles(values) { +function percentiles(values: number[]) { const sorted = [...values].sort((a, b) => a - b); return Object.fromEntries([50, 95, 99].map(p => [`p${p}`, +sorted[Math.ceil(sorted.length * p / 100) - 1].toFixed(2)])); } -async function join(api, user, roomId, index) { +async function join(api: ReturnType, user: { id: string }, roomId: string, index: number) { const room = new SynixirRoom({ roomId, userId: user.id, serverUrl: origin, getAccess: async ({ signal }) => { const { data } = await api(`/api/rooms/${roomId}/token`, "POST", undefined, signal); @@ -105,7 +106,7 @@ try { await until(() => clients.every(({ room }) => room.state.saveStatus === "saved") && groups.every(group => group.every(({ room }) => room.doc.getMap("load").size === group.length)), "warmup"); const before = await metrics(); - for (const { room } of clients) room.doc.on("update", (update, source) => { + for (const { room } of clients) room.doc.on("update", (update: { byteLength: number; }, source: string) => { if (source === "load") updateBytes += update.byteLength; }); const phaseStarted = performance.now(); @@ -143,7 +144,7 @@ try { for (const writer of groups[0]) assert.equal(observer.room.doc.getMap("load").get(`writer_${writer.index}`), writes - 1); await Promise.all(clients.map(({ room }) => room.destroy())); assert.equal(process.listenerCount("exit"), exitListeners, "provider exit handlers released"); - let finalMetrics; + let finalMetrics = await metrics(); for (let attempt = 0; attempt < 50; attempt++) { finalMetrics = await metrics(); if (finalMetrics.active_channels === 0) break; diff --git a/scripts/staging-pilot.js b/scripts/staging-pilot.ts similarity index 79% rename from scripts/staging-pilot.js rename to scripts/staging-pilot.ts index 01f41dd..da2e644 100644 --- a/scripts/staging-pilot.js +++ b/scripts/staging-pilot.ts @@ -2,18 +2,20 @@ import assert from "node:assert/strict"; import { randomBytes } from "node:crypto"; import { readFile, writeFile } from "node:fs/promises"; import https from "node:https"; -import { chromium, expect } from "@playwright/test"; +import { BrowserContext, type Page, type BrowserContextOptions, chromium, expect } from "@playwright/test"; const [mode, baseURL, manifestPath] = process.argv.slice(2); assert.equal(new URL(baseURL).hostname, "localhost", "pilot is restricted to local staging"); assert.equal(new URL(baseURL).protocol, "https:"); const browser = await chromium.launch({ headless: true }); -const contexts = []; -const pageErrors = []; -const failedRequests = []; +const contexts: BrowserContext[] = []; +const pageErrors: string[] = []; +const failedRequests: { path: string; error?: string|undefined; status?: number; }[] = []; const password = randomBytes(24).toString("hex"); -async function context(storageState) { +type Storage = BrowserContextOptions["storageState"]; +interface Manifest { roomId: string; content: string; title: string; accounts: Record } +async function context(storageState?: Storage) { const value = await browser.newContext({ baseURL, ignoreHTTPSErrors: true, storageState }); value.on("page", page => { page.on("pageerror", error => pageErrors.push(error.message)); @@ -30,27 +32,29 @@ async function context(storageState) { return value; } -async function api(ctx, path, method = "GET", data) { +async function api(ctx: BrowserContext, path: string, method = "GET", data?: Record) { const session = await (await ctx.request.get("/api/session")).json(); return ctx.request.fetch(path, { method, data, headers: { "x-csrf-token": session.csrf_token } }); } -async function account(role) { +async function account(role: string) { const ctx = await context(); const username = `pilot_${role}_${randomBytes(5).toString("hex")}`; const response = await api(ctx, "/api/accounts", "POST", { username, password }); assert.equal(response.status(), 200, await response.text()); const { user } = await response.json(); const cookie = (await ctx.cookies()).find(cookie => cookie.name === "_synixir_key"); + assert.ok(cookie); assert.equal(cookie.secure, true); assert.equal(cookie.httpOnly, true); assert.equal(cookie.sameSite, "Lax"); return { ctx, user, username, password }; } -async function open(ctx, roomId, role) { +async function open(ctx: BrowserContext, roomId: string, role: string|RegExp|readonly (string|RegExp)[]) { const page = await ctx.newPage(); const response = await page.goto(`/?room=${roomId}`); + assert.ok(response); assert.equal(response.status(), 200); assert.equal(response.headers()["cache-control"], "no-store"); try { @@ -66,27 +70,27 @@ async function open(ctx, roomId, role) { return page; } -async function text(page, expected) { - await expect.poll(() => page.locator(".cm-line").evaluateAll(lines => lines.map(line => { - const copy = line.cloneNode(true); +async function text(page: Page, expected: string) { + await expect.poll(() => page.locator(".cm-line").evaluateAll((lines) => lines.map(line => { + const copy = line.cloneNode(true) as HTMLElement; copy.querySelectorAll(".cm-ySelectionCaret, .cm-placeholder").forEach(node => node.remove()); return copy.textContent; }).join("\n")), { timeout: 15000 }).toBe(expected); } -async function saved(page) { +async function saved(page: Page) { await expect(page.locator("#save-status")).toHaveText("Saved", { timeout: 15000 }); } -async function rejectsOrigin(origin) { +async function rejectsOrigin(origin: string) { const url = new URL("/socket/websocket?vsn=2.0.0", baseURL); return new Promise((resolve, reject) => { const req = https.request(url, { rejectUnauthorized: false, headers: { Origin: origin, Connection: "Upgrade", Upgrade: "websocket", "Sec-WebSocket-Version": "13", "Sec-WebSocket-Key": randomBytes(16).toString("base64"), } }); - req.on("response", response => { response.resume(); resolve(response.statusCode); }); - req.on("upgrade", (_response, socket) => { socket.destroy(); reject(new Error("Untrusted origin upgraded")); }); + req.on("response", (response: { resume: () => void; statusCode: unknown; }) => { response.resume(); resolve(response.statusCode); }); + req.on("upgrade", (_response: any, socket: { destroy: () => void; }) => { socket.destroy(); reject(new Error("Untrusted origin upgraded")); }); req.on("error", reject); req.setTimeout(5000, () => req.destroy(new Error("Origin rejection timed out"))); req.end(); @@ -98,7 +102,7 @@ try { const owner = await account("owner"), editor = await account("editor"), viewer = await account("viewer"); const roomId = `pilot_${randomBytes(8).toString("hex")}`; assert.equal((await api(owner.ctx, "/api/rooms", "POST", { room_id: roomId })).status(), 200); - for (const [person, role] of [[editor, "editor"], [viewer, "viewer"]]) { + for (const [person, role] of [[editor, "editor"], [viewer, "viewer"]] as const) { assert.equal((await api(owner.ctx, `/api/rooms/${roomId}/members/${person.username}`, "PUT", { role })).status(), 200); } const first = await open(owner.ctx, roomId, "owner"); @@ -133,6 +137,12 @@ try { await expect(settings.locator("#sdk-state")).toHaveText("connected · saved", { timeout: 15000 }); await settings.locator("#sdk-title").fill("Packaged SDK settings"); await expect(settings.locator("#sdk-state")).toHaveText("connected · saved"); + // Exercise every exported route and its lazy-loaded editor bundle in the release. + for (const route of ["kanban", "whiteboard", "rich-text", "multiplayer-form", "flowchart", "table"]) { + await settings.goto(`/${route}.html?room=${roomId}`); + await expect(settings.locator("#status")).toHaveText("Connected", { timeout: 15000 }); + await expect(settings.locator("#editor")).toBeVisible(); + } assert.equal((await owner.ctx.request.get("/metrics")).status(), 404); const forwarded = await owner.ctx.request.get("/api/session", { headers: { "X-Forwarded-Proto": "http", "X-Forwarded-For": "203.0.113.44" }, maxRedirects: 0 }); @@ -140,8 +150,8 @@ try { assert.equal(await rejectsOrigin("https://untrusted.example"), 403); assert.equal(await rejectsOrigin("https://localhost"), 403, "origin port must match"); - const manifest = { roomId, content: content + " offline", title: "Packaged SDK settings", accounts: {} }; - for (const [role, person] of [["owner", owner], ["editor", editor], ["viewer", viewer]]) { + const manifest: Manifest = { roomId, content: content + " offline", title: "Packaged SDK settings", accounts: {} }; + for (const [role, person] of [["owner", owner], ["editor", editor], ["viewer", viewer]] as const) { manifest.accounts[role] = { username: person.username, password: person.password, id: person.user.id, storage: await person.ctx.storageState() }; } @@ -155,10 +165,10 @@ try { headers: { "x-csrf-token": session.csrf_token, "x-forwarded-for": `203.0.113.${n}` } }); assert.ok([401, 429].includes(last.status())); } - assert.equal(last.status(), 429, "forwarded address must not bypass throttling"); + assert.equal(last!.status(), 429, "forwarded address must not bypass throttling"); console.log("Pilot passed: packaged pages, HTTPS cookies, collaboration, roles, offline edits, origin and proxy checks"); } else if (mode === "verify") { - const manifest = JSON.parse(await readFile(manifestPath, "utf8")); + const manifest: Manifest = JSON.parse(await readFile(manifestPath, "utf8")); for (const [role, person] of Object.entries(manifest.accounts)) { const ctx = await context(person.storage); const session = await (await ctx.request.get("/api/session")).json(); diff --git a/scripts/support/database.exs b/scripts/support/database.exs index f74e134..22b19e5 100644 --- a/scripts/support/database.exs +++ b/scripts/support/database.exs @@ -202,7 +202,9 @@ defmodule Synixir.Script.Database do S.run( [ "node", - "scripts/operations/load.js" + "--import", + "tsx", + "scripts/operations/load.ts" | Enum.map([port, clients, rooms, writes], &to_string/1) ], env: env diff --git a/scripts/support/staging.exs b/scripts/support/staging.exs index e4a2416..6d6e753 100644 --- a/scripts/support/staging.exs +++ b/scripts/support/staging.exs @@ -317,7 +317,9 @@ defmodule Synixir.Script.Stage do S.run( [ "node", - "scripts/staging-pilot.js", + "--import", + "tsx", + "scripts/staging-pilot.ts", "verify", stage.url, Path.join(stage.directory, "pilot.json") @@ -335,7 +337,9 @@ defmodule Synixir.Script.Stage do S.private_json(manifest, %{}) try do - S.run(["node", "scripts/staging-pilot.js", "seed", stage.url, manifest], capture: false) + S.run(["node", "--import", "tsx", "scripts/staging-pilot.ts", "seed", stage.url, manifest], + capture: false + ) rescue error -> if S.json(manifest) == %{}, do: File.rm!(manifest) diff --git a/scripts/tsconfig.json b/scripts/tsconfig.json new file mode 100644 index 0000000..39fa4af --- /dev/null +++ b/scripts/tsconfig.json @@ -0,0 +1,7 @@ +{ + "compilerOptions": { + "target": "ES2022", "lib": ["ES2024", "DOM"], "module": "NodeNext", "moduleResolution": "NodeNext", + "types": ["node"], "strict": true, "noEmit": true, "skipLibCheck": true, "allowImportingTsExtensions": true + }, + "include": ["**/*.ts"] +} diff --git a/test/synixir_web/controllers/client_controller_test.exs b/test/synixir_web/controllers/client_controller_test.exs index 6820127..c729200 100644 --- a/test/synixir_web/controllers/client_controller_test.exs +++ b/test/synixir_web/controllers/client_controller_test.exs @@ -21,7 +21,7 @@ defmodule SynixirWeb.ClientControllerTest do assert html_response(conn, 200) == File.read!(path) else assert response(conn, 404) == - "Client assets are not built. Use the Vite development server." + "Client assets are not built. Use the Next.js development server." end end end