sercrod

*stage

Aliases: *stage, n-stage
Category: host-level data control

Summary

*stage enables a two-phase editing model on a Sercrod host.
Instead of mutating the host data directly, the runtime creates a deep copy called the staged buffer.
All bindings inside the host work against this staged buffer, and you can later commit or discard those changes using other directives such as *apply and *restore.

The attribute value of *stage or n-stage is currently not evaluated. Presence alone enables staging.

Basic example

@@@html <serc-rod data={ user: { name: "Alice", email: "alice@example.com" } } *stage>

<label>
  Email
  <input *input="user.email">
</label>

<p>Staged preview: <span *print="user.name"></span></p>

<button type="button" *apply>Apply</button>
<button type="button" *restore>Reset</button>
@@@

In this configuration:

Behavior

At runtime, Sercrod maintains two internal objects per host:

When a Sercrod host is created:

When the host renders:

When inputs bound with *input or n-input fire:

Evaluation timing

*stage affects when and where data is cloned, but it does not introduce its own expression language or custom timing. The key timings are:

No additional scheduling hooks are introduced by *stage itself. It changes which object is edited and rendered, not when expressions are evaluated.

Execution model

Conceptually, *stage splits the host data into:

The execution model is:

  1. Host initialises _data.
  2. If *stage is present:
    • _stage is created as a deep copy of _data.
    • All template expressions inside the host see _stage.
  3. While the user edits:
    • *input and n-input writes go into _stage.
    • Other directives that mutate data (such as *load) also prefer _stage over _data.
  4. When the user commits:
    • *apply copies _stage back into _data and refreshes _stage from the new committed state.
  5. When the user discards:
    • *restore throws away the current _stage contents and recreates _stage from the last committed snapshot.

Internally, Sercrod first tries structuredClone to create _stage. If that fails, it falls back to JSON.parse(JSON.stringify(...)). This means *stage is primarily intended for JSON like data (plain objects, arrays, numbers, strings, booleans, and null).

Variable creation

*stage does not create new variables in the template scope. It only changes which data object is used as the root when resolving existing expressions.

In other words, the same expressions you would write without *stage continue to work. They now read and write from the staged buffer instead of the committed data when staging is active.

Scope layering

The behavior of *stage depends on whether the Sercrod host is a root host or a nested host.

In both cases, expressions inside the host are evaluated against this._stage ?? this._data. Parent scopes above the host are not changed by *stage unless you explicitly propagate changes using *apply on a nested host or other application specific code.

Interaction with bindings and data directives

*stage changes the behavior of several other directives that work on host data:

Other directives that only read data (such as *print, *textContent, or conditions and loops) automatically see the staged values when *stage is present, because they evaluate against the same effective scope.

Interaction with *apply and *restore

*stage is designed to be used together with *apply and *restore.

This trio (*stage plus *apply plus *restore) is the recommended pattern for implementing editable but confirmable forms.

Use with conditionals and loops

*stage does not introduce any new restrictions on conditionals or loops. Instead, it transparently changes the data that those directives see:

Examples:

@@@html <serc-rod data={ form: { dirty: false } } *stage>

You have unsaved changes.

@@@

@@@html <serc-rod data={ filter: "", items: [] } *stage> <input *input="filter">

<button type="button" *apply>Apply filter and items @@@

In both cases, the loop and conditional behavior is identical to non staged code, but they operate on staged values.

Best practices

Additional examples

Simple staged form with save and reset:

@@@html <serc-rod data={ profile: { name: "", bio: "" } } *stage>

Edit profile (staged)

Committed name:

Staged name:

<button type="button" *apply>Apply to committed profile <button type="button" *restore>Discard staged changes @@@

Staged load and post flow:

@@@html <serc-rod data={ form: {} } *stage> <input type="file" *load> <button type="button" *post="/api/preview">Send staged form to preview API



		

<button type="button" *apply>Apply staged form to committed data @@@

Here:

Notes