VisualSkeleton — Plug‑and‑Code Template for CAMI Visuals
This repository is a zero‑configuration template to build visuals for the CAMI (Cellular Automata) host application. Everything is already wired: Vite, React, Tailwind, linting, and the messaging bridge with the host app. You can start coding your visualization logic immediately — no setup overhead.
— TL;DR: clone, npm i, npm run dev, then implement your renderer in src.
What You Get
- React 18 + Vite 7: fast dev server and optimized builds.
- Tailwind v4 via
@tailwindcss/vite: utility‑first styling, no config. - MUI ready: accessible UI controls out of the box.
- PIXI.js stack: ready to render large grids efficiently if you choose to.
- ESLint 9 (React hooks + refresh): sane defaults and CI‑friendly rules.
- Single JS bundle output: the build produces one self‑contained file for easy embedding in CAMI.
Requirements
- Node.js 18+ recommended
- CAMI host application (for integration and messaging)
Quick Start
git clone https://github.com/Cellular-Automaton/VisualSkeleton.git
cd VisualSkeleton
npm install
npm run dev Open the dev URL printed by Vite. Start editing files in src/ — hot reload is enabled.
Build and Output
npm run build The build is configured in library mode to emit a single ES module with CSS injected into JS:
- Output directory:
dist/ - File name:
GameOfLifeVisual.js - Format:
es(ES module)
You can preview the production build locally:
npm run preview Project Structure
src/
App.jsx # Example UI and message handlers
main.jsx # Entry point (library build)
index.css # Tailwind entry
hooks/
useGrid.js # Minimal grid state helper (example)
components/ # Add your renderer components here Key config files:
vite.config.js: library build, single‑file output, CSS injected.eslint.config.js: recommended rules, React hooks, refresh.package.json:dev,build,preview,lintscripts.
Messaging Contract with the Host
The visual communicates with the CAMI host through a thin API exposed in Electron. Messages are plain objects of the shape:
{
action: "ACTION_NAME",
data?: any
} Send messages to the host:
window.electronAPI.sendToHost({
action: 'PLAY_SIMULATION',
data: {
/* ... */
}
}); Listen for messages from the host:
window.addEventListener('message', (event) => {
const { action, data } = event.data;
// handle(action, data)
}); Supported actions and payloads implemented in this template:
UPDATE_TABLE(from host):{ data: { table: number[] } }- Updates the current grid state. Length must match the number of cells.
PARAMETERS(from host):{ data: { parameters: Record<string, unknown> } }- Provides algorithm parameters. Extend handling as needed.
IMPORTED_DATA(from host):{ data: { table: number[], parameters: Record<string, unknown> } }- Loads a saved grid and parameters.
PLAY_SIMULATION(from visual):{ data: { parameters: Record<string, unknown>, table: number[] } }- Signals the host to start computation from the provided initial state.
PAUSE_SIMULATION(from visual):- No
datafield. Pauses the host computation.
- No
EXPORT(from visual):{ data: { table: number[], parameters: Record<string, { value: unknown, type: string }> } }- Asks the host to persist the current visual state.
IMPORT(from visual):- No
datafield. Asks the host to provide saved data viaIMPORTED_DATA.
- No
See src/App.jsx for working examples of all handlers above.
How To Adapt This Template
Note: You do not have to use the example grid state (useGrid) or a grid at all. You can implement your own state model and renderer (Canvas/WebGL, PIXI, Three.js, DOM, typed arrays, etc.). The only constraints are:
- Respect the messaging contract with the host (actions and payloads).
- If the host sends
UPDATE_TABLE, translate that payload into your internal model (or adapt your protocol if your host integration expects a different shape). - Keep the build single‑file as configured (library mode + no CSS splitting).
- Keep the public entry at
src/main.jsxor updatevite.config.jsaccordingly.
- Implement your renderer in
src/components/and use it fromApp.jsx.
- Your renderer receives the grid (
cells,rows,cols) and UI callbacks.
- Define your simulation parameters in
App.jsx.
- Store them in the
parametersstate. They are sent to the host withPLAY_SIMULATIONand included inEXPORT.
- Handle host messages according to your automaton.
- Ensure incoming
tablelengths matchrows * cols. - Maintain a frames buffer if you want time‑scrubbing.
- Keep the build single‑file.
- Library mode +
inlineDynamicImports+cssCodeSplit: falseensures one JS file. If you add code‑splitting, revisitvite.config.js.
Linting
npm run lint Rules are set to be strict on unused variables and React hooks best practices.
Troubleshooting
window.electronAPIis undefined:- You’re running in a plain browser. The host (Electron) injects this API. For standalone dev, guard calls or mock the API.
Output isn’t a single file:
- Check
vite.config.jsforbuild.lib,inlineDynamicImports: true, andcssCodeSplit: false.
- Check
Table length mismatch errors:
- Ensure
table.length === rows * cols. Recreate the grid when parameters change.
- Ensure
License
This repository is provided as a template for building visuals for the CAMI host application. See the repository’s LICENSE if/when added by your project.