An interactive web application that visualizes and compares sorting algorithms side by side in real time.
All algorithms sort the same shuffled dataset at the same playback speed, making it intuitive to observe differences in strategy, comparisons, and data movement.
- Side-by-Side Comparison: Run multiple sorting algorithms simultaneously against identical input arrays.
- Merge Sort Walkthrough: See the remaining values on each side, the selected value, and the result as it is built. Bar labels show values rather than original positions.
- Display Selection & Order: Toggle algorithms above the playback controls and drag their handles to reorder panels. Keyboard users can press Space, move with arrow keys, and press Space to drop or Escape to cancel. Hidden algorithms keep their playback state.
- Interactive Controls: Play, pause, step forward, shuffle, and customize both array size (5–50 elements) and animation speed (0.2×–10×).
- Dual Language & Theming: Full Japanese / English localization (auto-detected with manual override) and responsive layouts.
- Extensible Architecture: Pure step-generator functions completely decoupled from the React rendering layer.
Sorting execution and visual playback are completely separated:
flowchart LR
Input["Initial Shuffled Array"] --> Gen["Pure Step Generator<br>(buildSteps)"]
Gen -->|"Array of Step objects<br>(compare, swap, pivot, etc.)"| State["Playback Engine<br>(App State & Timer)"]
State -->|"Immutable Board State"| UI["React Components<br>(Bar Rendering & Overlays)"]
- Step Generation: Each algorithm implements a pure function that precomputes the entire sorting process into an array of immutable
Stepobjects (e.g., compare, swap, write). - Playback: The React app steps through the list per tick of the animation timer, applying updates to the state without re-running sorting logic.
- Framework: React 18, TypeScript
- UI & Styling: Mantine UI, Vanilla CSS
- Bundler & Tooling: Parcel, Vitest, Testing Library, ESLint, Prettier
The project is configured to run inside Docker, so local Node.js installation is optional.
The Node.js version is set in .nvmrc. CI and the production build on Render read it, and the Docker image uses the same major version. Update them together.
# Start dev server with hot reload
docker compose up -d --buildThe application is served at:
- Japanese: http://localhost:1234/ja/
- English: http://localhost:1234/en/
Each checkout, including each git worktree, runs as its own Compose project with its own container. They all publish host port 1234 by default, though, so only one dev server can use it at a time. To run another checkout's server alongside, pick a different host port with DEV_PORT:
DEV_PORT=1235 docker compose up -d --buildThat server is then at http://localhost:1235/ja/ and http://localhost:1235/en/. Pass the same DEV_PORT whenever you run docker compose up again in that checkout; docker compose exec does not need it.
If your editor supports VS Code Dev Containers:
- Open this repository in your container-supported editor.
- Select Reopen in Container. Dependencies will be installed automatically.
Run commands inside the running container:
# Execute tests
docker compose exec -T app bash -c 'yarn test'
# Type check, lint, format check, and test in one go
docker compose exec -T app bash -c 'yarn typecheck && yarn lint && yarn format && yarn test'| Command | Description |
|---|---|
yarn dev |
Starts Parcel dev server |
yarn build |
Builds the production bundle into public/, with asset URLs pointing to the production site so Open Graph images are absolute |
yarn typecheck |
Validates TypeScript types (tsc --noEmit) |
yarn lint / yarn lint:fix |
Runs ESLint / applies auto-fixes |
yarn format / yarn format:fix |
Checks / applies Prettier formatting |
yarn test |
Runs Vitest unit and UI test suite |
A Husky pre-commit hook runs lint-staged on staged files. It runs inside the app container when that service is running, and on the host otherwise.
In a git worktree, the container mounts only the worktree, so the shared .git directory is not visible inside it. The hook therefore always runs on the host there, which requires host dependencies. Install them with the Node.js version from .nvmrc (for example, after nvm use):
COREPACK_ENABLE_AUTO_PIN=0 yarn install --frozen-lockfileFor the same reason, Husky's prepare script prints a git error during the container's install in a worktree. The error is harmless and does not stop the install.
src/
├── App.tsx # Main layout, playback loop, and board states
├── plugins/ # Step types and algorithm step generators
├── components/ # Shared UI controls, bars, and per-algorithm overlays
│ ├── ControlBar.tsx
│ ├── SortSection.tsx
│ └── algorithms/ # Visual representations & legends for each algorithm
├── ja/, en/ # Localized HTML templates and dictionary strings
└── __tests__/ # Logic tests and UI component specifications