Skip to content

UI Reference

This page describes every element of the projectMM web UI. For how to set up the system, see getting-started.md. For the API behind the UI, see developer-guide/api.md.


Status Bar

The status bar runs across the top of the page on every screen.

Element What it shows
Device name The name set in SystemStatusModule (name control). Defaults to projectMM.
WebSocket dot Connection state: green = connected, grey = connecting / disconnected.
Reconnect Click the dot when grey to trigger an immediate reconnect attempt.
Hamburger menu (☰) Opens the navigation panel (see below).

The hamburger menu (☰) opens a side panel listing all top-level modules, grouped by category. Click a module name to jump to its card on the page.

The selected module is remembered across page reloads using localStorage — the page scrolls back to the last selected module automatically on reconnect.

On mobile, the menu collapses automatically after selecting a module.


Module Cards

Each module is displayed as a card. The card header shows:

  • Module name and type (e.g. SineEffectModule)
  • Category badge (effect, modifier, layout, driver, system, network)
  • Enabled checkbox — uncheck to disable the module's loop() without removing it. State is persisted.
  • Timing — average loop time in milliseconds (when available)
  • Remove button — removes the module at runtime (not available for permanent modules or modules with children)

Child modules are indented under their parent card.


Controls

Controls are rendered from each module's JSON schema. The supported control types are:

Slider

A horizontal range input.

  • Drag or type a value to change it.
  • Changes are sent to the server immediately via POST /api/control.
  • The slider does not move while being dragged (dragging is suppressed until you release), preventing the WebSocket state push from overwriting a mid-drag position.
  • Min, max, and step come from the module's schema.

Toggle (checkbox)

A checkbox for boolean values.

  • Click to toggle.
  • Used for the enabled control on every module, and for boolean module-specific controls.

Select (dropdown)

A <select> element for enumerated values.

  • Change the dropdown to send the new value immediately.

Text input

A plain text field for string values.

  • Sensitive fields (e.g. passwords) are rendered as type="password" inputs — the current value is never shown.
  • Press Enter or blur to submit.

Live Preview (WebGL canvas)

The PreviewModule card displays a live WebGL canvas showing the current pixel buffer.

  • The canvas fills the available card width, with a maximum height of 50% of the viewport (max-height: 50vh), so module controls remain visible below it.
  • Pixels are rendered at native resolution, scaled up with nearest-neighbour interpolation (image-rendering: pixelated).
  • The preview updates at ~50 Hz when the WebSocket is connected.
  • The canvas pauses when the browser tab is hidden and resumes when it becomes visible.

Add Module

Click the + button in the module list header to open the add-module panel.

  1. Select a type from the dropdown (populated from GET /api/types).
  2. Enter an id (must be unique).
  3. Optionally select a parent module.
  4. Click Add.

The new module appears immediately in the list.


Remove Module

Each non-permanent module card has a × (remove) button. Clicking it:

  1. Sends DELETE /api/modules/<id> to the server.
  2. If the module has children, the request fails — remove children first.
  3. If the module is permanent (e.g. SystemStatusModule), the request fails.

Reboot Button

The SystemStatusModule card includes a Reboot button. On ESP32 this triggers a hardware reset; on PC it exits the process.


WebSocket reconnect behaviour

  • When the tab is hidden, the WebSocket stays open but frame processing pauses (wsPaused = true). This prevents pixel buffer backlog on mobile.
  • A 25-second heartbeat ping prevents NAT/carrier idle timeouts.
  • When the tab becomes visible again (or is restored from the browser's back-forward cache on Safari), the connection resumes immediately without a reconnect.
  • If the connection drops (network error or server restart), the frontend reconnects with exponential backoff starting at 500 ms.