From ac8cdbffd0061e715659247af8318a42e72c9a33 Mon Sep 17 00:00:00 2001 From: skratchdot Date: Wed, 9 Sep 2026 08:24:13 -0400 Subject: [PATCH 1/2] Move playground state and lifecycle ownership into React --- docs/architecture.md | 27 +- oxlint.config.ts | 10 +- package.json | 5 + pnpm-lock.yaml | 443 +++++++++++++++++- src/audio/audio-settings.ts | 15 + src/{controllers => audio}/create-player.mjs | 8 +- src/components/dialogs/algorithm-dialog.tsx | 19 +- src/components/dialogs/midi-dialog.tsx | 12 +- src/components/layout/site-document.tsx | 3 +- .../playground/browser-playground.tsx | 55 +-- .../playground/playback-controls.tsx | 27 +- src/components/playground/player-section.tsx | 2 +- .../playground/playground-context.tsx | 57 +++ .../playground/settings-controls.tsx | 10 +- src/components/playground/sort-sidebar.tsx | 28 +- .../playground/sorting-playground.tsx | 62 +-- .../playground/waveform-controls.tsx | 15 +- .../visualizations/player-chart.tsx | 29 +- src/controllers/connect-audio-settings.ts | 55 --- src/controllers/connect-playback-settings.ts | 48 -- src/controllers/connect-sort-settings.ts | 48 -- src/controllers/create-playground.mjs | 298 ------------ src/controllers/playground-types.ts | 3 - src/hooks/use-players.ts | 107 +++++ src/hooks/use-sort.ts | 150 ++++++ src/{controllers => midi}/create-helpers.mjs | 2 +- src/sorting/input-data.ts | 43 ++ src/state/algorithm-overrides.ts | 2 +- src/state/envelope.ts | 2 +- src/state/playback-preferences.ts | 4 +- src/state/players.ts | 34 ++ src/state/settings.ts | 2 +- src/state/waveforms.ts | 2 +- test/audio-settings-connection.test.mjs | 108 ----- test/browser/site.spec.mjs | 37 ++ test/playback-settings-connection.test.mjs | 77 --- test/react-playground.test.mjs | 187 ++++++++ test/scales.test.mjs | 2 +- test/settings.test.mjs | 8 +- test/sort-settings-connection.test.mjs | 74 --- test/ui.test.mjs | 20 +- test/waveforms.test.mjs | 6 +- 42 files changed, 1250 insertions(+), 896 deletions(-) create mode 100644 src/audio/audio-settings.ts rename src/{controllers => audio}/create-player.mjs (92%) create mode 100644 src/components/playground/playground-context.tsx delete mode 100644 src/controllers/connect-audio-settings.ts delete mode 100644 src/controllers/connect-playback-settings.ts delete mode 100644 src/controllers/connect-sort-settings.ts delete mode 100644 src/controllers/create-playground.mjs delete mode 100644 src/controllers/playground-types.ts create mode 100644 src/hooks/use-players.ts create mode 100644 src/hooks/use-sort.ts rename src/{controllers => midi}/create-helpers.mjs (92%) create mode 100644 src/sorting/input-data.ts create mode 100644 src/state/players.ts delete mode 100644 test/audio-settings-connection.test.mjs delete mode 100644 test/playback-settings-connection.test.mjs create mode 100644 test/react-playground.test.mjs delete mode 100644 test/sort-settings-connection.test.mjs diff --git a/docs/architecture.md b/docs/architecture.md index 17f6288..1dbbe01 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -13,7 +13,7 @@ Paths below are relative to `src/`; `@/` aliases that directory. | `components/playground/`, `components/dialogs/` | Interactive controls, editor, and MIDI export | | `components/ui/` | Shared shadcn Base UI primitives | | `components/visualizations/` | React SVG charts and envelope diagram | -| `controllers/` | Playground/player coordination and settings connections | +| `hooks/` | React effects for players, audio settings, sorting, and cleanup | | `state/` | Jotai settings, envelopes, playback preferences, and algorithm overrides | | `sorting/` | Recorder, worker protocol, registries, and algorithm implementations | | `generators/`, `midi/` | Input patterns, musical data, and MIDI encoding | @@ -31,11 +31,21 @@ About/API import Markdown from `docs/` through `components/docs-page.tsx` and remain readable without JavaScript. Browser audio and editor modules do not run during prerendering. -`controllers/create-playground.mjs` coordinates data generation, workers, settings -connections, sample preloading, and two players. Each player publishes frames, -position, and visualization selection. React reads runtime snapshots through -`useSyncExternalStore` and settings through the application-scoped Jotai store. -Audio clocks, nodes, and sample caches stay outside React and Jotai. +The document's Jotai `Provider` owns state across client-side route changes. +Components use `useAtomValue`, `useAtom`, and `useSetAtom` directly. Settings, +algorithm overrides, visualization choice, and player snapshots live in atoms; +there is no module-level store or parallel subscription system. + +`components/playground/playground-context.tsx` provides playback, editing, and +export actions. `hooks/use-players.ts` creates and disposes the audio resources, +then synchronizes waveform, volume, tempo, instrument, and visualization through +React effects. `hooks/use-sort.ts` manages input data, sample preloading, and +worker requests; effect cleanup cancels pending edits and ignores stale replies. + +Audio clocks, nodes, and sample caches are imperative services owned by the +player effect. Their callbacks read current values from the provider's store +through `audio/audio-settings.ts`, without creating their own subscriptions. +Player callbacks write snapshots into atoms for React to render. React owns SVG children; D3 supplies array, scale, color, and path utilities. Trajectory geometry is memoized by frame data. The waveform preview canvas is @@ -46,8 +56,9 @@ Base UI handles dialog focus and dismissal. Ace loads on demand, with pending initialization cancelled on close. Tab panels stay mounted for editor/canvas lifetime. Invalid source leaves the algorithm catalog unchanged. -Cached-page suspension disconnects effects, pauses audio, cancels workers and -pending resumes, and closes dialogs. Returning reconnects without automatically playing. +Cached-page suspension pauses audio immediately, cancels workers and pending +resumes, and closes dialogs. React effects synchronize current settings when the +page returns, without automatically playing. Leaving Home disposes the playground and its owned resources. The shared AudioContext remains library-owned. diff --git a/oxlint.config.ts b/oxlint.config.ts index ca4d28a..788ab26 100644 --- a/oxlint.config.ts +++ b/oxlint.config.ts @@ -1,7 +1,7 @@ import { defineConfig } from "oxlint"; export default defineConfig({ - plugins: ["eslint", "unicorn", "oxc", "vitest", "typescript", "jsx-a11y"], + plugins: ["eslint", "unicorn", "oxc", "vitest", "typescript", "jsx-a11y", "react"], categories: { correctness: "error" }, env: { node: true }, ignorePatterns: [ @@ -16,6 +16,8 @@ export default defineConfig({ "test-results/**", ], rules: { + "react/rules-of-hooks": "error", + "react/exhaustive-deps": "error", // SVG/canvas images and live status regions intentionally use ARIA roles. "jsx-a11y/prefer-tag-over-role": "off", "typescript/consistent-type-definitions": ["error", "type"], @@ -25,6 +27,12 @@ export default defineConfig({ "one-var": ["error", "never"], }, overrides: [ + { + // These effects initialize browser-only resources or read the client clock + // after hydration; neither operation can run during prerendering. + files: ["src/hooks/use-players.ts", "src/components/layout/footer.tsx"], + rules: { "react/set-state-in-effect": "off" }, + }, { files: ["src/components/docs-page.tsx"], // Keyboard users must be able to scroll wide documentation tables. diff --git a/package.json b/package.json index 946e88d..cd5edc7 100644 --- a/package.json +++ b/package.json @@ -61,13 +61,18 @@ "@axe-core/playwright": "^4.13.0", "@playwright/test": "^1.63.0", "@tailwindcss/vite": "^4.3.3", + "@testing-library/react": "^16.3.3", + "@types/d3-array": "^3.2.2", "@types/d3-color": "^3.1.3", + "@types/d3-scale": "^4.0.9", "@types/d3-shape": "^3.2.0", + "@types/file-saver": "^2.0.7", "@types/node": "^26.5.0", "@types/react": "^19.2.18", "@types/react-dom": "^19.2.7", "@vitejs/plugin-react": "^6.1.1", "cspell": "^10.2.2", + "jsdom": "^30.0.1", "oxfmt": "^0.67.0", "oxlint": "^1.82.0", "sirv-cli": "^3.0.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 02d7535..8c42918 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -186,12 +186,24 @@ importers: '@tailwindcss/vite': specifier: ^4.3.3 version: 4.3.3(vite@8.2.2(@types/node@26.5.0)(jiti@2.7.0)(yaml@2.9.0)) + '@testing-library/react': + specifier: ^16.3.3 + version: 16.3.3(@testing-library/dom@10.4.1)(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@types/d3-array': + specifier: ^3.2.2 + version: 3.2.2 '@types/d3-color': specifier: ^3.1.3 version: 3.1.3 + '@types/d3-scale': + specifier: ^4.0.9 + version: 4.0.9 '@types/d3-shape': specifier: ^3.2.0 version: 3.2.0 + '@types/file-saver': + specifier: ^2.0.7 + version: 2.0.7 '@types/node': specifier: ^26.5.0 version: 26.5.0 @@ -207,6 +219,9 @@ importers: cspell: specifier: ^10.2.2 version: 10.2.2 + jsdom: + specifier: ^30.0.1 + version: 30.0.1 oxfmt: specifier: ^0.67.0 version: 0.67.0 @@ -227,10 +242,18 @@ importers: version: 8.2.2(@types/node@26.5.0)(jiti@2.7.0)(yaml@2.9.0) vitest: specifier: ^5.0.0 - version: 5.0.0(@types/node@26.5.0)(vite@8.2.2(@types/node@26.5.0)(jiti@2.7.0)(yaml@2.9.0)) + version: 5.0.0(@types/node@26.5.0)(jsdom@30.0.1)(vite@8.2.2(@types/node@26.5.0)(jiti@2.7.0)(yaml@2.9.0)) packages: + '@asamuzakjp/css-color@6.0.7': + resolution: {integrity: sha512-vC/bk1Lz7Tn/EfU9/apOTBk80/8dyGyWMowPoV1tJ52muDGsDqt2HPT2klrFUiY60MQmQv9q8yIht15JnBgDGw==} + engines: {node: ^22.13.0 || >=24.0.0} + + '@asamuzakjp/dom-selector@8.3.2': + resolution: {integrity: sha512-93Z1N+BQNXysodoicpOIyNh2drHfz/CTf9nnT0FEx72GJcIiwgydD7tGAr78j41LsYn3hlRn+LdGPuBLn1Bl8Q==} + engines: {node: ^22.13.0 || >=24.0.0} + '@axe-core/playwright@4.13.0': resolution: {integrity: sha512-6YLx+kxXu5GJceG4ozFg+33a2EMTdjYwWGloJ3sb9Kta5pp+ZNS53uxGVog5JetIY8s++P5UrtX+cri+u0VAVg==} peerDependencies: @@ -400,6 +423,10 @@ packages: '@types/react': optional: true + '@bramus/specificity@2.4.2': + resolution: {integrity: sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==} + hasBin: true + '@cspell/cspell-bundled-dicts@10.2.2': resolution: {integrity: sha512-327sQUcfHjr9TnkX2FYluLuQHSoWjSm9AiPFBpULxHofk7VvKfye12Ur4xMbOzyv7cxMROx/OeYF0oytLuLsvQ==} engines: {node: '>=22.18.0'} @@ -634,6 +661,42 @@ packages: resolution: {integrity: sha512-KC+Rzi9mExB1rzxoT3ysC98tGhiSbgkOXc6U8fbJmoggFswfwwJEzwa2NUNX75Thlty5ZzaPandLS4P0dh4kOA==} engines: {node: '>=22.18.0'} + '@csstools/color-helpers@6.1.1': + resolution: {integrity: sha512-gLNsunvwf3mCi5u5o46/Z/JcJMnhbHSaZ69rkgPzNM3J4s8hWwpPUQB6/tt0EDFyCiWzxANlx+2LJwpYj4zS1w==} + engines: {node: '>=20.19.0'} + + '@csstools/css-calc@3.3.0': + resolution: {integrity: sha512-c5ihYsPkdG6JCkU2zTMm4+k6r7RXuGxtWYhu5DHMIiF1FHzrfmHL5so11AoFpUv/tu61xfcmT4AmKoFfMPoqdQ==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-parser-algorithms': ^4.0.0 + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-color-parser@4.2.2': + resolution: {integrity: sha512-3QKjR/vxyjcSXBLgb6lP0S3MGdvwbmqSsvLPbYdVORqPDc8FX1HAJ0Spk38bxaRXgvENTA47tlhhbb5Z2e8hEg==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-parser-algorithms': ^4.0.0 + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-parser-algorithms@4.0.0': + resolution: {integrity: sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-syntax-patches-for-csstree@1.1.12': + resolution: {integrity: sha512-3vLQK+dXxhBMR2Wx99PTCifE+vHtW2ndZWyla8yK813ev6oGhyn8Lja8jCyGAWTJ+LEYZK7EVtJxrDj8ztevJw==} + peerDependencies: + css-tree: ^3.2.1 + peerDependenciesMeta: + css-tree: + optional: true + + '@csstools/css-tokenizer@4.0.0': + resolution: {integrity: sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==} + engines: {node: '>=20.19.0'} + '@dotenvx/dotenvx@1.75.1': resolution: {integrity: sha512-/BITOC9dmS/edY2zQwZNicQ059O6RKabtQfyEafV0nGtfYRNHYy1DIPiYVcov40+tob9hfmBnbR963dS+EQ1DQ==} hasBin: true @@ -641,6 +704,15 @@ packages: '@dotenvx/primitives@0.8.0': resolution: {integrity: sha512-VYJy0uhFm9zTJ1TxBaW/pA8bjbOM/OttaNMwZ1RHG4JKyRG7DhSdiqD1ipQoAyoD22olUtxbP78W9xY3Wd11bg==} + '@exodus/bytes@1.15.1': + resolution: {integrity: sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + peerDependencies: + '@noble/hashes': ^1.8.0 || ^2.0.0 + peerDependenciesMeta: + '@noble/hashes': + optional: true + '@floating-ui/core@1.8.0': resolution: {integrity: sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==} @@ -1304,21 +1376,52 @@ packages: resolution: {integrity: sha512-uhOeFyxLcU41HzvrxsGpiWdcMbScY1EDgbZ5K7DVRMYInbLYWAC0EA/kx9wXAoSM8q82bUG2hRl8+EAjE6XAbA==} engines: {node: '>=20.19'} + '@testing-library/dom@10.4.1': + resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} + engines: {node: '>=18'} + + '@testing-library/react@16.3.3': + resolution: {integrity: sha512-Uo193NgQbPMz6lrrhtRQQFcMC6Re/ELLFbbuVL30WDlZxlpZf9/lMHTAVxPRLw1q1iu9OJmR1c2BLiENRstdBg==} + engines: {node: '>=18'} + peerDependencies: + '@testing-library/dom': ^10.0.0 + '@types/react': ^18.0.0 || ^19.0.0 + '@types/react-dom': ^18.0.0 || ^19.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@ts-morph/common@0.27.0': resolution: {integrity: sha512-Wf29UqxWDpc+i61k3oIOzcUfQt79PIT9y/MWfAGlrkjg6lBC1hwDECLXPVJAhWjiGbfBCxZd65F/LIZF3+jeJQ==} + '@types/aria-query@5.0.4': + resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} + '@types/chai@5.2.3': resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + '@types/d3-array@3.2.2': + resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==} + '@types/d3-color@3.1.3': resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==} '@types/d3-path@3.1.1': resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==} + '@types/d3-scale@4.0.9': + resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==} + '@types/d3-shape@3.2.0': resolution: {integrity: sha512-kVd74ta9eof3eJOvbNd1vGKS/XERRyQbT26Og63hIsvDO84cjD5gEOhsXf26w3FSoNlPVz84DOFcKv/oou+fMw==} + '@types/d3-time@3.0.4': + resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==} + '@types/debug@4.1.13': resolution: {integrity: sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==} @@ -1331,6 +1434,9 @@ packages: '@types/estree@1.0.9': resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + '@types/file-saver@2.0.7': + resolution: {integrity: sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A==} + '@types/hast@3.0.5': resolution: {integrity: sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==} @@ -1551,6 +1657,10 @@ packages: resolution: {integrity: sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==} engines: {node: '>=12'} + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + ansis@4.3.1: resolution: {integrity: sha512-BJ8/l4R5LRE7hW9WdSuGYrLSHi2ynxeFpDFbH0K/CgNeY/tyhk+vO6TYxXC5r5CpUhNVX310xzPsN/H9lCdfOA==} engines: {node: '>=14'} @@ -1558,6 +1668,9 @@ packages: argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + aria-query@5.3.0: + resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} + array-timsort@1.0.3: resolution: {integrity: sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==} @@ -1592,6 +1705,9 @@ packages: engines: {node: '>=6.0.0'} hasBin: true + bidi-js@1.1.0: + resolution: {integrity: sha512-fX1Onk0tdVPC7obPWB5EbJ1z7NVhLq4m2xZLq2YXBkxzMXIGRpNMU88n0EPgWseKl12J7zXs7qrDxPK4sRs2fg==} + body-parser@2.3.0: resolution: {integrity: sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==} engines: {node: '>=18'} @@ -1801,6 +1917,10 @@ packages: engines: {node: '>=22.18.0'} hasBin: true + css-tree@3.2.1: + resolution: {integrity: sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + cssesc@3.0.0: resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} engines: {node: '>=4'} @@ -1845,6 +1965,10 @@ packages: resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==} engines: {node: '>=12'} + data-urls@7.0.0: + resolution: {integrity: sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + debounce-fn@4.0.0: resolution: {integrity: sha512-8pYCQiL9Xdcg0UPSD3d+0KMlOjp+KGU5EPwYddgzQ7DATsg4fuUDjQtsYLmWjnk2obnNHgV3vE2Y4jejSOJVBQ==} engines: {node: '>=10'} @@ -1858,6 +1982,9 @@ packages: supports-color: optional: true + decimal.js@10.6.0: + resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} + decode-named-character-reference@1.3.0: resolution: {integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==} @@ -1908,6 +2035,9 @@ packages: resolution: {integrity: sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==} engines: {node: '>=0.3.1'} + dom-accessibility-api@0.5.16: + resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} + dot-prop@6.0.1: resolution: {integrity: sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==} engines: {node: '>=10'} @@ -1941,6 +2071,10 @@ packages: resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} engines: {node: '>=8.6'} + entities@8.1.0: + resolution: {integrity: sha512-kxL7msIffSuh9aaFAMD7rxAIuTRMAHMeBtgHW2yUdWw732ZNh4MehkF2gdjvtdmikkaIP9bFDDJOPlsvm7avrA==} + engines: {node: '>=20.19.0'} + env-paths@2.2.1: resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} engines: {node: '>=6'} @@ -2185,6 +2319,10 @@ packages: resolution: {integrity: sha512-c8/gF9ac8Y78/agExVocyLevgR+JlpNB444Py0FSX8pJoPdYUfUzRcXtYEYGwt6l19qIlVZPN5Mfsw9jFShmQQ==} engines: {node: '>=16.9.0'} + html-encoding-sniffer@6.0.0: + resolution: {integrity: sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + html-url-attributes@3.0.1: resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==} @@ -2303,6 +2441,9 @@ packages: resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} engines: {node: '>=12'} + is-potential-custom-element-name@1.0.1: + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + is-promise@4.0.0: resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} @@ -2381,6 +2522,15 @@ packages: resolution: {integrity: sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==} hasBin: true + jsdom@30.0.1: + resolution: {integrity: sha512-52v7mUVUfNQVYYqE1lcdaymWL0njO7lTLUog6ZvW2U5KsbiLk/GnZlVJ+qx0xfNJZ6Gn+KSpPNE52vurbxZwrA==} + engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0} + peerDependencies: + canvas: ^3.2.3 + peerDependenciesMeta: + canvas: + optional: true + jsesc@3.1.0: resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} engines: {node: '>=6'} @@ -2583,6 +2733,10 @@ packages: longest-streak@3.1.0: resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} + lru-cache@11.5.2: + resolution: {integrity: sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==} + engines: {node: 20 || >=22} + lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} @@ -2591,6 +2745,10 @@ packages: peerDependencies: react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + lz-string@1.5.0: + resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} + hasBin: true + magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} @@ -2649,6 +2807,9 @@ packages: mdast-util-to-string@4.0.0: resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + mdn-data@2.27.1: + resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==} + media-typer@1.1.1: resolution: {integrity: sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==} engines: {node: '>= 0.8'} @@ -2907,6 +3068,9 @@ packages: resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==} engines: {node: '>=18'} + parse5@8.0.1: + resolution: {integrity: sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==} + parseurl@1.3.3: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} engines: {node: '>= 0.8'} @@ -2982,6 +3146,10 @@ packages: engines: {node: '>=14'} hasBin: true + pretty-format@27.5.1: + resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + pretty-ms@9.3.1: resolution: {integrity: sha512-HzMy3Geq23nVALD/M2LliU+F+M+gVNsvkQWWqeBZ8HDiCgzo6YPJ/Omrmtq24EFrIsk0a3EkQGEd7bDOo+IhGA==} engines: {node: '>=18'} @@ -2997,6 +3165,10 @@ packages: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + qs@6.16.0: resolution: {integrity: sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==} engines: {node: '>=0.6'} @@ -3017,6 +3189,9 @@ packages: peerDependencies: react: ^19.2.8 + react-is@17.0.2: + resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + react-markdown@10.1.0: resolution: {integrity: sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==} peerDependencies: @@ -3096,6 +3271,10 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + saxes@6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} + scheduler@0.27.0: resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} @@ -3267,6 +3446,9 @@ packages: style-to-object@1.0.14: resolution: {integrity: sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==} + symbol-tree@3.2.4: + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + systeminformation@5.33.8: resolution: {integrity: sha512-v4F6OGYGh7wDvV68YmjOmZwGixV9A/GQ7d2b84t0UF4CaOy9jipNWIJDkHqYDYiTPuiojqlwVQd0hfUKOUN7tQ==} engines: {node: '>=10.0.0'} @@ -3306,6 +3488,13 @@ packages: resolution: {integrity: sha512-9YodfrxS9g9IbFr/KOjE5bAeJ0p61n3bW6mqvy0jtoeKd1kTW1Cxm0oulm6KX2lyM9Gl6WIe8nEbY7LWv5ZJww==} engines: {node: ^20.0.0 || >=22.0.0} + tldts-core@7.4.12: + resolution: {integrity: sha512-nYNzS2WRf4QJmjzFFgAxLOBjyBxAGRbCy9PVBPaglcYyYajh40VBn+v5Ngr96ZMc7oM0+aCJdtQnNejvdBnXMQ==} + + tldts@7.4.12: + resolution: {integrity: sha512-WylhSDKVeYnWXL3a+vKTaOxjnOeEGw938hImY8zoRWJjRRK/Jp1K+IihBzIONpUmW4e3WmXT6q5FW6vlESVZCA==} + hasBin: true + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -3318,6 +3507,14 @@ packages: resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} engines: {node: '>=6'} + tough-cookie@6.0.2: + resolution: {integrity: sha512-exgYmnmL/sJpR3upZfXG5PoatXQii55xAiXGXzY+sROLZ/Y+SLcp9PgJNI9Vz37HpQ74WvDcLT8eqm+kV3FzrA==} + engines: {node: '>=16'} + + tr46@6.0.0: + resolution: {integrity: sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==} + engines: {node: '>=20'} + trim-lines@3.0.1: resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} @@ -3356,6 +3553,10 @@ packages: resolution: {integrity: sha512-RYONW2MeafgYlkVOKYKkA/Ag7BmXqgIWCa8t1m0JcxrQg9pI9lEqRhAOruOBCbAohOa/gkCF+iPi9hrgvTzu6Q==} engines: {node: '>=20.18.1'} + undici@8.10.2: + resolution: {integrity: sha512-/y4/bH9YNU5hi9NIrpOuvGXFcxrj3CMrV+/AYpowAYTpHn8gX/XPFjNy766FPoYY0miQhdW977JFWKGNhBdwyQ==} + engines: {node: '>=22.19.0'} + unicorn-magic@0.3.0: resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==} engines: {node: '>=18'} @@ -3545,9 +3746,29 @@ packages: vscode-uri@3.2.0: resolution: {integrity: sha512-m2gXo3bn0G1kT9InzMf07fTbqMbGtyckj3bH5ktLO+1Ssv+yiATZ4dhwaQv9UZWxJh6E9IFGnQyjgWVDWVBDrg==} + w3c-xmlserializer@5.0.0: + resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} + engines: {node: '>=18'} + + webidl-conversions@8.0.1: + resolution: {integrity: sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==} + engines: {node: '>=20'} + webpack-virtual-modules@0.6.2: resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} + whatwg-mimetype@5.0.0: + resolution: {integrity: sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==} + engines: {node: '>=20'} + + whatwg-url@16.0.1: + resolution: {integrity: sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + whatwg-url@17.1.0: + resolution: {integrity: sha512-3GeworPmc2ZfEEHP7lEbUfBX/L75wdEsi0rLNhXcXxnoN5jyq0SL5gCy06SGW2cyTIZdTvWIDQNQoza++vKeaw==} + engines: {node: ^22.14.0 || >=24.0.0} + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -3574,10 +3795,17 @@ packages: resolution: {integrity: sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ==} engines: {node: '>=12'} + xml-name-validator@5.0.0: + resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} + engines: {node: '>=18'} + xmlbuilder2@4.0.3: resolution: {integrity: sha512-bx8Q1STctnNaaDymWnkfQLKofs0mGNN7rLLapJlGuV3VlvegD7Ls4ggMjE3aUSWItCCzU0PEv45lI87iSigiCA==} engines: {node: '>=20.0'} + xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} @@ -3610,6 +3838,21 @@ packages: snapshots: + '@asamuzakjp/css-color@6.0.7': + dependencies: + '@csstools/css-calc': 3.3.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-color-parser': 4.2.2(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + lru-cache: 11.5.2 + + '@asamuzakjp/dom-selector@8.3.2': + dependencies: + bidi-js: 1.1.0 + css-tree: 3.2.1 + is-potential-custom-element-name: 1.0.1 + lru-cache: 11.5.2 + '@axe-core/playwright@4.13.0(playwright-core@1.63.0)': dependencies: axe-core: 4.13.0 @@ -3832,6 +4075,10 @@ snapshots: optionalDependencies: '@types/react': 19.2.18 + '@bramus/specificity@2.4.2': + dependencies: + css-tree: 3.2.1 + '@cspell/cspell-bundled-dicts@10.2.2': dependencies: '@cspell/dict-ada': 4.1.1 @@ -4054,6 +4301,30 @@ snapshots: '@cspell/url@10.2.2': {} + '@csstools/color-helpers@6.1.1': {} + + '@csstools/css-calc@3.3.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + + '@csstools/css-color-parser@4.2.2(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/color-helpers': 6.1.1 + '@csstools/css-calc': 3.3.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + + '@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/css-tokenizer': 4.0.0 + + '@csstools/css-syntax-patches-for-csstree@1.1.12(css-tree@3.2.1)': + optionalDependencies: + css-tree: 3.2.1 + + '@csstools/css-tokenizer@4.0.0': {} + '@dotenvx/dotenvx@1.75.1': dependencies: '@dotenvx/primitives': 0.8.0 @@ -4075,6 +4346,8 @@ snapshots: '@dotenvx/primitives@0.8.0': {} + '@exodus/bytes@1.15.1': {} + '@floating-ui/core@1.8.0': dependencies: '@floating-ui/utils': 0.2.12 @@ -4623,25 +4896,56 @@ snapshots: '@tanstack/virtual-file-routes@1.162.0': {} + '@testing-library/dom@10.4.1': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/runtime': 7.29.7 + '@types/aria-query': 5.0.4 + aria-query: 5.3.0 + dom-accessibility-api: 0.5.16 + lz-string: 1.5.0 + picocolors: 1.1.1 + pretty-format: 27.5.1 + + '@testing-library/react@16.3.3(@testing-library/dom@10.4.1)(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@babel/runtime': 7.29.7 + '@testing-library/dom': 10.4.1 + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + optionalDependencies: + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) + '@ts-morph/common@0.27.0': dependencies: fast-glob: 3.3.3 minimatch: 10.2.6 path-browserify: 1.0.1 + '@types/aria-query@5.0.4': {} + '@types/chai@5.2.3': dependencies: '@types/deep-eql': 4.0.2 assertion-error: 2.0.1 + '@types/d3-array@3.2.2': {} + '@types/d3-color@3.1.3': {} '@types/d3-path@3.1.1': {} + '@types/d3-scale@4.0.9': + dependencies: + '@types/d3-time': 3.0.4 + '@types/d3-shape@3.2.0': dependencies: '@types/d3-path': 3.1.1 + '@types/d3-time@3.0.4': {} + '@types/debug@4.1.13': dependencies: '@types/ms': 2.1.0 @@ -4654,6 +4958,8 @@ snapshots: '@types/estree@1.0.9': {} + '@types/file-saver@2.0.7': {} + '@types/hast@3.0.5': dependencies: '@types/unist': 3.0.3 @@ -4788,10 +5094,16 @@ snapshots: ansi-regex@6.3.0: {} + ansi-styles@5.2.0: {} + ansis@4.3.1: {} argparse@2.0.1: {} + aria-query@5.3.0: + dependencies: + dequal: 2.0.3 + array-timsort@1.0.3: {} assertion-error@2.0.1: {} @@ -4819,6 +5131,10 @@ snapshots: baseline-browser-mapping@2.11.21: {} + bidi-js@1.1.0: + dependencies: + require-from-string: 2.0.2 + body-parser@2.3.0: dependencies: bytes: 3.1.2 @@ -5061,6 +5377,11 @@ snapshots: semver: 7.8.5 tinyglobby: 0.2.17 + css-tree@3.2.1: + dependencies: + mdn-data: 2.27.1 + source-map-js: 1.2.1 + cssesc@3.0.0: {} csstype@3.2.3: {} @@ -5099,6 +5420,13 @@ snapshots: dependencies: d3-array: 3.2.4 + data-urls@7.0.0: + dependencies: + whatwg-mimetype: 5.0.0 + whatwg-url: 16.0.1 + transitivePeerDependencies: + - '@noble/hashes' + debounce-fn@4.0.0: dependencies: mimic-fn: 3.1.0 @@ -5107,6 +5435,8 @@ snapshots: dependencies: ms: 2.1.3 + decimal.js@10.6.0: {} + decode-named-character-reference@1.3.0: dependencies: character-entities: 2.0.2 @@ -5138,6 +5468,8 @@ snapshots: diff@8.0.4: {} + dom-accessibility-api@0.5.16: {} + dot-prop@6.0.1: dependencies: is-obj: 2.0.0 @@ -5168,6 +5500,8 @@ snapshots: ansi-colors: 4.1.3 strip-ansi: 6.0.1 + entities@8.1.0: {} + env-paths@2.2.1: {} env-paths@4.0.0: @@ -5438,6 +5772,12 @@ snapshots: hono@4.13.7: {} + html-encoding-sniffer@6.0.0: + dependencies: + '@exodus/bytes': 1.15.1 + transitivePeerDependencies: + - '@noble/hashes' + html-url-attributes@3.0.1: {} http-errors@2.0.1: @@ -5518,6 +5858,8 @@ snapshots: is-plain-obj@4.1.0: {} + is-potential-custom-element-name@1.0.1: {} + is-promise@4.0.0: {} is-regexp@3.1.0: {} @@ -5563,6 +5905,32 @@ snapshots: dependencies: argparse: 2.0.1 + jsdom@30.0.1: + dependencies: + '@asamuzakjp/css-color': 6.0.7 + '@asamuzakjp/dom-selector': 8.3.2 + '@bramus/specificity': 2.4.2 + '@csstools/css-syntax-patches-for-csstree': 1.1.12(css-tree@3.2.1) + '@exodus/bytes': 1.15.1 + css-tree: 3.2.1 + data-urls: 7.0.0 + decimal.js: 10.6.0 + html-encoding-sniffer: 6.0.0 + is-potential-custom-element-name: 1.0.1 + lru-cache: 11.5.2 + parse5: 8.0.1 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 6.0.2 + undici: 8.10.2 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 8.0.1 + whatwg-mimetype: 5.0.0 + whatwg-url: 17.1.0 + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - '@noble/hashes' + jsesc@3.1.0: {} jsmidgen@0.1.8: {} @@ -5701,6 +6069,8 @@ snapshots: longest-streak@3.1.0: {} + lru-cache@11.5.2: {} + lru-cache@5.1.1: dependencies: yallist: 3.1.1 @@ -5709,6 +6079,8 @@ snapshots: dependencies: react: 19.2.8 + lz-string@1.5.0: {} + magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.6.0 @@ -5874,6 +6246,8 @@ snapshots: dependencies: '@types/mdast': 4.0.4 + mdn-data@2.27.1: {} + media-typer@1.1.1: {} merge-descriptors@2.0.0: {} @@ -6249,6 +6623,10 @@ snapshots: parse-ms@4.0.0: {} + parse5@8.0.1: + dependencies: + entities: 8.1.0 + parseurl@1.3.3: {} path-browserify@1.0.1: {} @@ -6298,6 +6676,12 @@ snapshots: prettier@3.9.6: {} + pretty-format@27.5.1: + dependencies: + ansi-regex: 5.0.1 + ansi-styles: 5.2.0 + react-is: 17.0.2 + pretty-ms@9.3.1: dependencies: parse-ms: 4.0.0 @@ -6314,6 +6698,8 @@ snapshots: forwarded: 0.2.0 ipaddr.js: 1.9.1 + punycode@2.3.1: {} + qs@6.16.0: dependencies: es-define-property: 1.0.1 @@ -6335,6 +6721,8 @@ snapshots: react: 19.2.8 scheduler: 0.27.0 + react-is@17.0.2: {} + react-markdown@10.1.0(@types/react@19.2.18)(react@19.2.8): dependencies: '@types/hast': 3.0.5 @@ -6459,6 +6847,10 @@ snapshots: safer-buffer@2.1.2: {} + saxes@6.0.0: + dependencies: + xmlchars: 2.2.0 + scheduler@0.27.0: {} semiver@1.1.0: {} @@ -6666,6 +7058,8 @@ snapshots: dependencies: inline-style-parser: 0.2.7 + symbol-tree@3.2.4: {} + systeminformation@5.33.8: {} tailwindcss@4.3.3: {} @@ -6689,6 +7083,12 @@ snapshots: tinypool@2.1.2: {} + tldts-core@7.4.12: {} + + tldts@7.4.12: + dependencies: + tldts-core: 7.4.12 + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 @@ -6697,6 +7097,14 @@ snapshots: totalist@3.0.1: {} + tough-cookie@6.0.2: + dependencies: + tldts: 7.4.12 + + tr46@6.0.0: + dependencies: + punycode: 2.3.1 + trim-lines@3.0.1: {} trough@2.2.0: {} @@ -6751,6 +7159,8 @@ snapshots: undici@7.29.1: {} + undici@8.10.2: {} + unicorn-magic@0.3.0: {} unified@11.0.5: @@ -6842,7 +7252,7 @@ snapshots: optionalDependencies: vite: 8.2.2(@types/node@26.5.0)(jiti@2.7.0)(yaml@2.9.0) - vitest@5.0.0(@types/node@26.5.0)(vite@8.2.2(@types/node@26.5.0)(jiti@2.7.0)(yaml@2.9.0)): + vitest@5.0.0(@types/node@26.5.0)(jsdom@30.0.1)(vite@8.2.2(@types/node@26.5.0)(jiti@2.7.0)(yaml@2.9.0)): dependencies: '@types/chai': 5.2.3 '@vitest/mocker': 5.0.0(vite@8.2.2(@types/node@26.5.0)(jiti@2.7.0)(yaml@2.9.0)) @@ -6860,6 +7270,7 @@ snapshots: why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 26.5.0 + jsdom: 30.0.1 transitivePeerDependencies: - msw @@ -6867,8 +7278,32 @@ snapshots: vscode-uri@3.2.0: {} + w3c-xmlserializer@5.0.0: + dependencies: + xml-name-validator: 5.0.0 + + webidl-conversions@8.0.1: {} + webpack-virtual-modules@0.6.2: {} + whatwg-mimetype@5.0.0: {} + + whatwg-url@16.0.1: + dependencies: + '@exodus/bytes': 1.15.1 + tr46: 6.0.0 + webidl-conversions: 8.0.1 + transitivePeerDependencies: + - '@noble/hashes' + + whatwg-url@17.1.0: + dependencies: + '@exodus/bytes': 1.15.1 + tr46: 6.0.0 + webidl-conversions: 8.0.1 + transitivePeerDependencies: + - '@noble/hashes' + which@2.0.2: dependencies: isexe: 2.0.0 @@ -6891,6 +7326,8 @@ snapshots: xdg-basedir@5.1.0: {} + xml-name-validator@5.0.0: {} + xmlbuilder2@4.0.3: dependencies: '@oozcitak/dom': 2.0.2 @@ -6898,6 +7335,8 @@ snapshots: '@oozcitak/util': 10.0.0 js-yaml: 4.3.2 + xmlchars@2.2.0: {} + yallist@3.1.1: {} yaml@2.9.0: {} diff --git a/src/audio/audio-settings.ts b/src/audio/audio-settings.ts new file mode 100644 index 0000000..55267db --- /dev/null +++ b/src/audio/audio-settings.ts @@ -0,0 +1,15 @@ +import type { useStore } from "jotai"; +import { settingsAtom } from "../state/settings"; +import { selectedWaveformAtom } from "../state/waveforms"; + +// Audio clock callbacks read current values without subscribing or owning a store. +export function createAudioSettings(store: ReturnType) { + return { + getSelected(key: string, fallback?: unknown) { + const selected = store.get(settingsAtom); + return Object.hasOwn(selected, key) ? selected[key as keyof typeof selected] : fallback; + }, + getSelectedWaveformInfo: () => store.get(selectedWaveformAtom), + getTempoString: () => `bpm${store.get(settingsAtom).tempo} l16`, + }; +} diff --git a/src/controllers/create-player.mjs b/src/audio/create-player.mjs similarity index 92% rename from src/controllers/create-player.mjs rename to src/audio/create-player.mjs index 1206751..677436f 100644 --- a/src/controllers/create-player.mjs +++ b/src/audio/create-player.mjs @@ -1,9 +1,9 @@ -import { createTimbreAudio } from "../audio/create-timbre-audio.mjs"; -import { createTransport } from "../audio/create-transport.ts"; -import { timbre } from "../audio/timbre.mjs"; +import { createTimbreAudio } from "./create-timbre-audio.mjs"; +import { createTransport } from "./create-transport.ts"; +import { timbre } from "./timbre.mjs"; import { visualizations } from "../visualizations/visualization-types.ts"; import { createMidiBytes } from "../midi/create-midi-bytes.mjs"; -import { drawStringPreview } from "../audio/string-preview.ts"; +import { drawStringPreview } from "./string-preview.ts"; // Owns transport and audio resources; React renders the published chart data. export function createPlayer({ settings, getMidiNumber, soundfont, isLooping, onUpdate }) { diff --git a/src/components/dialogs/algorithm-dialog.tsx b/src/components/dialogs/algorithm-dialog.tsx index 5613741..74d4f78 100644 --- a/src/components/dialogs/algorithm-dialog.tsx +++ b/src/components/dialogs/algorithm-dialog.tsx @@ -1,6 +1,6 @@ import { Button } from "@/components/ui/button"; import { useEffect, useRef, useState } from "react"; -import { useAtomValue } from "jotai"; +import { useAtomValue, useSetAtom } from "jotai"; import { settingsAtom } from "../../state/settings.ts"; import { algorithmCatalogAtom, @@ -11,17 +11,14 @@ import { algorithms } from "../../sorting/algorithm-registry.mjs"; import { sources } from "../../sorting/algorithm-sources.mjs"; import { getFunctionBody } from "../../sorting/sort-requests.ts"; import type { createCodeEditor } from "./create-code-editor.mjs"; -import type { Props } from "../../controllers/playground-types.ts"; import { PlayerDialog as Dialog } from "./player-dialog.tsx"; import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"; import { Input } from "@/components/ui/input"; -export function AlgorithmDialog({ - runtime, - adding, - onClose, -}: Props & { adding: boolean; onClose: () => void }) { - const { sort } = useAtomValue(settingsAtom, { store: runtime.store }); - const catalog = useAtomValue(algorithmCatalogAtom, { store: runtime.store }); +export function AlgorithmDialog({ adding, onClose }: { adding: boolean; onClose: () => void }) { + const addAlgorithm = useSetAtom(addAlgorithmAtom); + const editAlgorithm = useSetAtom(editAlgorithmAtom); + const { sort } = useAtomValue(settingsAtom); + const catalog = useAtomValue(algorithmCatalogAtom); const algorithm = catalog[sort]!; const [name, setName] = useState(""); const [tab, setTab] = useState(adding ? "algorithm" : "information"); @@ -70,12 +67,12 @@ export function AlgorithmDialog({ try { if (adding) { if (!name.trim()) return; - runtime.store.set(addAlgorithmAtom, { + addAlgorithm({ id: `custom_${crypto.randomUUID()}`, name: name.trim(), source: editor.current.getValue(), }); - } else runtime.store.set(editAlgorithmAtom, { id: sort, source: editor.current.getValue() }); + } else editAlgorithm({ id: sort, source: editor.current.getValue() }); onClose(); } catch (error) { setSaveError(error instanceof Error ? error.message : String(error)); diff --git a/src/components/dialogs/midi-dialog.tsx b/src/components/dialogs/midi-dialog.tsx index ec0160d..8d1e473 100644 --- a/src/components/dialogs/midi-dialog.tsx +++ b/src/components/dialogs/midi-dialog.tsx @@ -1,15 +1,13 @@ +import { usePlayground } from "../playground/playground-context"; import { useState } from "react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { NativeSelect } from "@/components/ui/native-select"; import { instruments } from "../../midi/instruments.ts"; import { PlayerDialog as Dialog } from "./player-dialog.tsx"; -import type { Props, PlayerId } from "../../controllers/playground-types.ts"; -export function MidiDialog({ - runtime, - id, - onClose, -}: Props & { id: PlayerId; onClose: () => void }) { +import type { PlayerId } from "../../state/players"; +export function MidiDialog({ id, onClose }: { id: PlayerId; onClose: () => void }) { + const actions = usePlayground(); const [name, setName] = useState(""); const [placeholder] = useState(() => `AudioSort_${Date.now()}`); const [channel, setChannel] = useState(0); @@ -24,7 +22,7 @@ export function MidiDialog({ type="button" id="midi-export-btn" variant="default" - onClick={() => runtime.exportMidi(id, name.trim() || placeholder, channel, instrument)} + onClick={() => actions.exportMidi(id, name.trim() || placeholder, channel, instrument)} > Export As Midi diff --git a/src/components/layout/site-document.tsx b/src/components/layout/site-document.tsx index 314c7da..769d6f0 100644 --- a/src/components/layout/site-document.tsx +++ b/src/components/layout/site-document.tsx @@ -1,3 +1,4 @@ +import { Provider } from "jotai"; import type { ReactNode } from "react"; import { HeadContent, Scripts } from "@tanstack/react-router"; import { Header } from "./header.tsx"; @@ -25,7 +26,7 @@ export function SiteDocument({ children }: { children: ReactNode }) {
- {children} + {children}