Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/deploy-pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,12 @@ jobs:
- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Lint, format, test, and build
run: pnpm run check

- name: Install Chromium
run: pnpm exec playwright install --with-deps chromium

- name: Lint, format, test, and build
run: pnpm run check

- name: Test the built site and worker
run: pnpm run test:browser

Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/pr-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,11 @@ jobs:
- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Lint, format, test, and build
run: pnpm run check

- name: Install Chromium
run: pnpm exec playwright install --with-deps chromium

- name: Lint, format, test, and build
run: pnpm run check

- name: Test the built site and worker
run: pnpm run test:browser
27 changes: 19 additions & 8 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand All @@ -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
Expand All @@ -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.

Expand Down
9 changes: 7 additions & 2 deletions docs/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,14 @@ repository's Pages source to **GitHub Actions**. No Node server is deployed.
| `pnpm run format` / `pnpm run format:check` | Format files / check formatting |
| `pnpm run spellcheck` | CSpell checks |
| `pnpm run typecheck` | Strict checking of application TypeScript; `.mjs` is not type-checked |
| `pnpm test` / `pnpm run test:watch` | Unit tests / watch mode |
| `pnpm test` / `pnpm run test:watch` | Node and Chromium hook tests / watch mode |

Run browser tests against a completed build:
Vitest runs pure unit tests in Node and React hook tests in Chromium through its
Playwright provider. Install Chromium once with `pnpm exec playwright install chromium`
before running `pnpm test` or `pnpm run check`. To run only Node tests, use
`pnpm test --project=node`.

The separate Playwright suite tests the completed production build:

```sh
pnpm exec playwright install chromium
Expand Down
10 changes: 9 additions & 1 deletion oxlint.config.ts
Original file line number Diff line number Diff line change
@@ -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: [
Expand All @@ -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"],
Expand All @@ -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.
Expand Down
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,17 @@
"@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",
"@vitest/browser-playwright": "^5.0.0",
"cspell": "^10.2.2",
"oxfmt": "^0.67.0",
"oxlint": "^1.82.0",
Expand Down
Loading