I/O
A small live-sensor readout panel — the canonical example of the Arsyn module structure.
/* ── Module: Sensor Monitor — thin entry ──
* Registers the module on App.modules and wires up its parts.
* Every Arsyn module exposes render(vp): it draws into the given
* viewport element and (optionally) returns a cleanup function.
*/
import { sensorState } from './sensor-state.js';
import { renderPanel } from './sensor-ui.js';
App.modules.sensor = Object.assign(sensorState, {
render(vp) {
// vp is the viewport container Arsyn hands you.
vp.innerHTML = `
<div class="sensor-view" style="padding:1rem">
<h2>Sensor Monitor</h2>
<div id="sensor-readout" class="sensor-readout">–</div>
<button id="sensor-toggle" class="btn btn-primary">Start</button>
</div>
`;
renderPanel(vp, sensorState);
// Return a cleanup function — Arsyn calls it when the module closes.
return () => sensorState.stop();
},
});
/* ── Sensor state + logic (no DOM in here) ── */
export const sensorState = {
_timer: null,
value: 0,
running: false,
start(onTick) {
if (this.running) return;
this.running = true;
this._timer = setInterval(() => {
this.value = Math.round(Math.random() * 100);
onTick?.(this.value);
}, 500);
},
stop() {
this.running = false;
clearInterval(this._timer);
this._timer = null;
},
};
/* ── Sensor UI bindings (DOM only) ── */
export function renderPanel(vp, state) {
const readout = vp.querySelector('#sensor-readout');
const toggle = vp.querySelector('#sensor-toggle');
toggle.addEventListener('click', () => {
if (state.running) {
state.stop();
toggle.textContent = 'Start';
} else {
state.start(v => { readout.textContent = v + ' units'; });
toggle.textContent = 'Stop';
}
});
}
# Sensor Monitor A tiny example Arsyn module. It shows the standard shape of every module: - **module-sensor.js** — thin entry, registers on `App.modules` - **sensor-state.js** — state & logic, no DOM - **sensor-ui.js** — DOM bindings only ## Install Drop these files into your Arsyn `modules/` folder and reload the app.