# Actions

> The action editor: identity and location, the script block, what a script may read and write, every validation message, the Run verb and its journal, Enable and Disable, Copy and Delete, and the run limits.

An action is a JavaScript statement block that reads the address space and writes memory variables and tags. Actions are the write path of Logic: they run on demand (Run), on a [trigger](logic-triggers), or from a dashboard [Button](component-button) wired to them. Select an action under the Actions section of the Logic column (or create one with New… > New action) to open its editor; reading needs **View logic**, editing **Configure logic**, running **Operate logic**.

The header shows the action mark, the name, the line "A script you run on demand, or that a trigger runs for you, writing variables and tags." and a **Disabled** pill while the action is off. The form column holds IDENTITY and SCRIPT; the rail holds LAST RUN.

## Identity {#identity}

| Field | What it is | Values / default | Effect |
| --- | --- | --- | --- |
| Name | The display name and the label a run is journaled under. | Free text; a new action is `ActionN`; a blank name saves as `Action`. | Autosaves per keystroke. Actions are referenced by identity, so renaming rewrites nothing and breaks nothing. |
| Location line | Where the action is filed, in words: `Actions / Startup / Walk setpoint`. | Read only. | An action has no address; the line is a place, never something to type into an expression. Change it with **Move to** in the tree. |
| Description (optional) | Free text. | Empty by default. | None. |

## Script {#script}

The SCRIPT card is the [script editor](logic-expressions#the-script-editor) with the hint "JavaScript statements (let, if, for, functions). Write memory variables by assignment (Variables.Setpoint = 21.5;) and tags with Connector.Driver.Device.Tag.Write(value). Reads are not quality-gated: check .State() and guard gaps with ?? before arithmetic." The script autosaves per keystroke and is validated every half second; the message appears under the hint. Validation never executes the script.

A script is ordinary strict-mode JavaScript (locals, `if`, loops, functions, `Math`, `Date`, `JSON`) over the same [address space](logic-address-space) and the same [read surface](logic-expressions#the-read-surface-of-a-point) as expressions, with two differences. Reads are **not** quality-gated: a gap reads as `null`, so check `.State()` and guard arithmetic with `??`; handling a bad reading is what actions are for. And a script may read expression variables (their published value); only an expression may not reference them.

### What an action may write {#what-an-action-may-write}

Exactly two constructs mutate the plant, and both go through the journaled [write funnel](logic#writing-values):

| Write | Form | Target |
| --- | --- | --- |
| Memory assignment | `Variables.Setpoint = 21.5;` or `Variables.Forno.Setpoint = 21.5;` | A memory variable, folder levels included. The value is coerced to the variable's output type and marked for retention when the variable is retentive. |
| Tag write | `Connector.Simulated.Pump.Cmd.Write(true);` | A live tag. The value is an engineering value: it travels backwards through the tag's [conversion pipeline](connector-value-pipeline) to the raw value the device takes. |

A write is write-through: what applied before a failure stays applied, and the journal shows each write as old to new under the source Action. A failed write throws, aborting that run with the reason in its outcome:

- "Write to 'X' rejected: the value is not finite." (NaN or infinity)
- "'Tag' is not materialized in the connector." (a disabled device, or a tag the embedded server does not carry)
- "'value' could not be converted for 'Tag'."
- "'X' is write-protected by '`<holder>`'." (a recipe run or an interlock holds the source)
- "Write to 'Tag' failed. Is the server running?"
- "Variable 'X' was not found." and "'Variables.X' is an expression variable: it is computed, never written."

## What validation refuses {#what-validation-refuses}

| Message | Cause |
| --- | --- |
| A script is required. | The editor is empty. |
| Script error: … | The text does not parse. |
| Addresses must be static: 'Root' is accessed with a computed segment. | `Variables[name]` or any non-literal segment. |
| Incomplete address '…'. | A reference too short to name a point. |
| 'X' was not found. | A read, or an assignment target, that resolves to nothing. |
| Assign to the variable itself: Variables.Name = value. | The left side of `=` names a member, or goes deeper than a variable's address can. |
| 'X' is an expression variable: it is computed, never written. | Assigning to an expression variable. |
| 'Connector' points are read-only. Write tags with Connector.Driver.Device.Tag.Write(value). | Assigning to any root other than Variables (Process has its own message). |
| Process points are read-only in Actions; run commands and staging writes are not supported by scripts. | Assigning to, or calling `Write` on, a Process point. |
| Process run commands cannot be called from Actions; command points expose read-only readiness values. | Calling `Process.<Model>.<Unit>.Commands.Start()` or any other command point; a `Commands` point only reads whether the command could execute now. |
| The ++ and -- operators are not supported on points. Use Variables.Name = Variables.Name.ValueToReal() + 1. | `Variables.N++`. |
| Write(value) is the tag write: Connector.Driver.Device.Tag.Write(value). | `Write` called on something that is not a tag address. |

JavaScript locals (`let x = 1; x = x + 1;`) are untouched by these rules.

## Run {#run}

**Run** is the leading verb of the bar over an open action; it is drawn only for a role with **Operate logic** and greyed with "Runtime stopped" while the station is read-only (the engineering lock does not grey it: running is operating, not configuring). When the role also holds Configure logic, the draft in the editor is saved first and the run executes the saved definition; without it, the run executes the durable definition (the fields are read-only, so the two never differ).

Every run executes on the single logic queue with a fresh script engine, so no state leaks between runs and a run never interleaves a sweep, another action or an operator write. A run is aborted past 500 ms ("The script was aborted: it ran past the 500 ms time limit (infinite loop?)."), 100,000 statements or 4 MB of memory, each with a message naming the limit. A disabled action fails without running ("The action is disabled."); a stopped runtime answers "The logic runtime is not running." The feed reports "Run OK in 12 ms." or "Run failed: `<reason>`". Only the failure reaches the [Events](events) console, under the action's name together with what invoked it: Manual for the verb, the trigger's name for a scheduled run. It is journaled as a warning the first time, summarized while it keeps failing with the same cause, and noted once more when it recovers, and that recovery line is the one entry a successful run ever writes there. A run that simply succeeds, and every value it writes, is written only to the [detailed capture](events-console#detailed-capture): a script writing on every sweep produces values by the thousand, and a console filled with them is a console nobody can read the station's news in.

The LAST RUN rail shows when the action last ran (or "Never run."), "Duration: N ms", the last error in a caution note, and the reminder "Runs execute on the single logic queue. Only a failed run reaches the Events console; a run that succeeds, and every value it writes, is kept for the detailed capture. Triggers invoke actions on schedule." The rail belongs to the live action: an autosave never blanks it; a restart of the runtime does.

## Commands {#commands}

| Command | What it does | Greyed when | Not drawn when |
| --- | --- | --- | --- |
| Run | Saves the draft (with Configure logic) and queues one run. | Runtime stopped. | Role lacks Operate logic. |
| Enable / Disable | Flips Enabled; a disabled action refuses Run and idles every trigger bound to it. | Station locked or read-only. | Role lacks Configure logic. |
| Copy | Duplicates the action to the Actions root under a free name and opens the copy, which has no run recorded and no trigger bound to it. | Station locked or read-only. | Role lacks Configure logic. |
| Delete action | Asks "Delete 'X'? This removes the action. This cannot be undone." A trigger bound to it is left unbound, not deleted. | Station locked or read-only. | Role lacks Configure logic. |

The Actions root panel lists every action with its state in the Value column: "Enabled · last run `<when>`", with "· error" when the last run failed, or "Disabled".

## What actions do not do {#what-actions-do-not-do}

They do not dispatch Process run commands, stage a procedure or add a run comment (those points are read-only readiness and staging values); they do not acknowledge alarms; they do not run in parallel; they keep no state between runs and share no functions; and they are not fired by value changes or conditions, only by Run, a trigger's interval or a dashboard button.
