vroqjs.com

04-code-organization/05-actions.md

Actions

Actions

Actions represent explicit state transitions in Vroq apps. They are a key tool for keeping code debuggable and predictable.

Purpose

Actions describe **what happened**, not how the state changes. Reducers are responsible for applying the transition.

Clear actions make it easier to:

  • understand behavior through logs
  • debug issues
  • replay or inspect changes
  • keep UI and state transitions separate

Where actions live

Actions should live in the feature folder:

features/featureName/
  featureActions.js

Action design rules

Good actions:

  • use explicit names
  • represent a meaningful event
  • have clear payload structure

Examples:

  • FILES_ADD
  • FILES_REMOVE
  • DEBUGGER_SET_CLIENT

Avoid overly generic names like SET_DATA or UPDATE.

Action registration

Each feature actions file should export a feature-local registration function:

export function registerFeatureActions(store) {
  store.registerAction("FILES_ADD", {
    description: "Add a file to the list.",
    payloadSchema: {
      type: "object",
      properties: {
        file: { type: "object" }
      },
      required: ["file"]
    }
  });
}

This registration exists so MCP debugging tools can expose action capabilities, descriptions, and payload schemas.

Typical location:

features/featureName/featureActions.js

configureStore.js should call each feature registrar after store creation.

Example:

import { createStore } from "/vroqjs/system/store/createStore.js";
import { registerFilesActions } from "../../features/files/filesActions.js";

export function configureStore() {
  const store = createStore([...reducers], initialState);
  registerFilesActions(store);
  return store;
}

Action creators

Action creators may still live in the actions file and use the action() helper so schema metadata stays easy to define in one place.

Important usage rule

Inside normal app code, do **not** depend on feature action creators.

Application code should dispatch plain action objects directly:

store.dispatch({
  type: "FILES_ADD",
  payload: { file }
});

The action definitions and registration exist mainly for MCP debugging capabilities and schema visibility. They are not the preferred event dispatch mechanism inside the app itself.

Simplest recommended form

Prefer the compact payload definition form so the framework can automatically generate the debugging schema used by MCP tools.

import { action } from "/vroqjs/system/store/action.js";

const _addFile = action("FILES_ADD", {
  description: "Add a file to the list.",
  payload: {
    file: {
      type: "object",
      required: true,
      description: "The file object to add."
    }
  },
  examples: [{ file: { name: "a.txt" } }]
});

export const addFile = (file) => _addFile({ file });

The payload object is automatically converted into the full payloadSchema used by debugging tools. This keeps the action payload definition as a single source of truth and prevents mismatches between action creators and debugger schemas.

Rules

  • keep actions small
  • keep payloads explicit
  • avoid hidden state mutation

Final rule

Actions should clearly describe what happened so reducers and debugging tools can reason about state transitions.