All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

Entries for unreleased work are not written here directly. Each issue drops a fragment in changelog.d/; the fragments are assembled into the section below at release. See that README for the format and for when a change warrants an entry at all.

[2.1.1] 2026-08-24

Documentation-only patch release: the hexdocs package and README were overhauled. No code changes.

Changed

  • ADRs are no longer published to hexdocs. They remain in the repository under docs/adr/; hexdocs now carries only the user-facing guides.
  • README rebuilt around a quick start, a documentation index, and the badge row.
  • Guide and README links converted so cross-references resolve both on hexdocs and on GitHub.

Fixed

  • All 69 docstring reference warnings across lib/ fixed; mix docs now builds with zero warnings.

[2.1.0] 2026-08-24

Added

  • Statifier.Telemetry emits the [:statifier, :session, ...] events for any stepping driver, taking the driver as a leading atom, so a host stepping the pure interpreter without a Statifier.Session process can emit the same contract (ADR-0067).

Changed

[2.0.0] 2026-08-22

Statifier 2.0.0 is a ground-up rewrite of the engine. The interpreter is a literal port of the W3C SCXML Appendix D algorithm over a pure functional core that returns effects; sessions, timers, and invocations are layered on top of that core rather than woven through it; predicator remains the datamodel - safe, non-evaluative expressions, no ECMAScript. To a 1.x user the whole engine is new, so this entry is written as a migration document rather than a transcript of the rewrite: the entries below are where 2.0 differs from 1.x in public API and observable behavior. Re-implemented 1.x behavior is not listed.

The core public surface is four functions: Statifier.compile/2, Statifier.initialize/2, Statifier.send_event/2, and Statifier.active_leaf_states/1. Everything else - sessions, persistence, recording and replay, invoke handlers, telemetry - is built on the effect stream those functions return. docs/architecture.md is the map.

Added

  • Statifier.compile/1 compiles SCXML source straight to a Statifier.Machine, running the parse, lowering, validation, and compile stages in order and stopping at the first stage that fails. Failures from every stage are reported the same way: {:error, [error]}.
  • Statifier.initialize/2 runs a compiled Statifier.Machine's initialization macrostep to quiescence, Statifier.send_event/2 sends one event to a Statifier.MachineState and runs a macrostep to quiescence (accepting either a Statifier.Event or a plain name string), and Statifier.active_leaf_states/1 reads the active leaf configuration back as a MapSet of string ids.
  • Statifier.Compiler.compile/1 compiles a validated Statifier.Document into a Statifier.Machine: states interned to a flat, document-order tuple with parent pointers and descendant ranges, transitions and executable content given dense document-order indexes, and every cond/expr/<content> compiled once into Statifier.Machine.expr().
  • Statifier.compile/2 accepts an options list (compile/1's existing behavior is compile/2 with no options). The one recognized option, invoke_content_markup: true, is for compiling an <invoke><content> markup slice standalone; it is not a general validation off-switch.
  • Statifier.compile/2 stamps a content-hash chart identity onto every Statifier.Machine.t() it produces (Statifier.Machine.identity/1).
  • Statifier.Machine.warnings carries the validator's non-fatal findings on a successfully compiled machine.
  • Statifier.Machine.State and Statifier.Machine.Transition each carry an attribute_locations field, the source node's written-attribute value spans carried through compilation verbatim - the same treatment Statifier.Machine.Invoke already had. An entry exists only for an attribute the author actually wrote, so Map.has_key?/2 on the map answers "was this written or defaulted" for a transition's type or a history's type, which the compiled field's value alone cannot. The root state (index 0) carries the <scxml> element's own attribute spans; the interpreter-synthesized initial transition carries %{}.
  • Statifier.Parser.Location.resolve_span/4 maps a predicator expression span onto an absolute document location, entity and character references in the raw attribute text included.
  • <datamodel> / <data> are now supported, with both binding="early" and binding="late" respected: a <data>'s expr or child content is compiled and, under early binding, evaluated at document-initialization time; under late binding, evaluation is deferred until the owning state is entered.
  • <assign> is now supported: expr or child content writes a value at a deep location path (user.profile.name, items[0].sku), auto-vivifying intermediate maps and lists along the way.
  • <script> is now supported: a body is a predicator statement program, compiled at load and run against the session datamodel wherever executable content or a top-level child of <scxml> may appear. <script src> is rejected at load; a body outside predicator's statement grammar (var, compound assignment, object literals, typeof, function definitions) is deferred to runtime and raises error.execution when reached.
  • A transition's cond attribute is evaluated against the datamodel and gates whether the transition is selected: true enables it, false does not. A cond that fails to evaluate to true or false (an unbound variable, or any other non-boolean result) raises error.execution on the internal event queue instead of silently not enabling the transition, catchable in the same macrostep by a <transition event="error.execution"> on the currently active state.
  • Statifier.EventData.coerce/1: one normalization function that turns raw <content> text, a <param> list, or an already-evaluated expression value into _event.data, per spec B.2.8.1.
  • Statifier.Session is a new GenServer that runs a state chart to completion: start_link/2 starts one, send_event/2 delivers events asynchronously, interpret/2 hands it externally-produced effects, cancel/1 stops it as <cancel> would, snapshot/1 and status/1 read its current position, and subscribe/2/unsubscribe/2 manage the subscriber pids that receive its effect stream.
  • Statifier.Supervisor, the optional session runtime an embedder places in their own supervision tree, holding the library's session registry and the dynamic supervisor sessions start on. The library ships no application callback, so a host that never places it starts no processes.
  • Statifier.start_session/2, which starts a session on that runtime and registers it, making it reachable by #_scxml_<sessionid> from another session. A bare Statifier.Session.start_link/2 session stays legal and unregistered, and is not reachable that way.
  • <send> targets beyond the sending session: #_internal/_internal route to the internal queue, #_scxml_<sessionid> delivers to the named registered session (the sending session's own id delivers to its own inbox with no registry), #_parent (or _parent) reaches the invoking parent, and #_<invokeid> delivers to that live invoked child's external queue. A target naming a session, parent, or invocation that is not reachable raises error.communication on the sending session, per the SCXML Event I/O Processor (C.1). Delivered events carry origin, origintype, and sendid per spec 5.10.1/C.1.
  • <send delay>/delayexpr accepts a native predicator duration value (e.g. a computed 2s + backoff) as well as a string, and recognizes the full {y,mo,w,d,h,m,s,ms} unit set rather than the SCXML schema's {ms,s,m,h,d} subset.
  • <invoke> is now lowered, validated, and compiled, including <param>, <content>, and <finalize>. The interpreter runs the Appendix D invoke and cancel-invoke passes and emits {:invoke, _}, {:cancel_invoke, _}, and {:autoforward, _} effects; <finalize> runs in the core before transition selection.
  • <invoke type="scxml"> starts a real child session, under an embedder-placed Statifier.Supervisor. The child's datamodel is seeded from <param>/namelist values whose names match one of its own top-level <data> ids; the parent monitors the child and the child monitors the parent. <send target="#_parent"> reaches the parent, running the invoking state's <finalize> first; a child that reaches a top-level final delivers done.invoke.<invokeid> to the parent carrying its <donedata>; every external event the parent processes is forwarded verbatim to each autoforward="true" invocation; and exiting the invoking state stops the child (running its <onexit> handlers) and discards any of its events still queued at the parent.
  • <invoke><content><scxml>...</scxml></content></invoke> compiles, and starts the inline child document as a session. <content>'s element children are preserved as the verbatim source they were written as, and compiled when the invocation runs; a <content> that specifies both an expr and inline markup is rejected as the same 5.6.2 violation as expr alongside inline text. A child <scxml> that declares no xmlns at all is placed in the SCXML namespace per spec Appendix G.6; a child that declares a namespace other than SCXML's raises error.communication when the invocation runs, and markup whose root uses a namespace prefix declared on an ancestor outside <content> is not supported (the declaration is lost with the slice).
  • A new invoke_source option on Statifier.start_session/2 and Statifier.Session.start_link/2: a function src -> {:ok, Machine.t()} | {:error, term()} an embedder supplies to resolve <invoke src="...">. The library never fetches a src itself - with no invoke_source configured, a src-based <invoke> raises error.communication on the parent, the same as any other failure to start the invoked child.
  • Hosts can register their own <invoke> handlers for types beyond the built-in scxml (and its long-URI spelling): a new Statifier.Invoke.Handler behaviour and a per-session invoke_handlers: %{type_string => module} option on Statifier.Session.start_link/2. A handler implements three pure planning callbacks (start/2, cancel/2, forward/3) and one optional impure perform/2; the built-in scxml handling runs behind this same interface. perform/2 may be called more than once for the same invoke_id after a crash and retry, so handlers must be idempotent on it - the library performs no deduplication of its own. An <invoke> whose type names no registered handler raises error.execution.
  • A new Statifier.Session.done_invocation/3 reports a non-scxml invocation's completion back to its owning session, constructing done.invoke.<invoke_id> from the caller-supplied donedata - the door a process-less or externally-run service uses in place of a child session's own done transition.
  • Statifier.Session.invocations/1 lists a session's live invocations as %{invoke_id, session_id, pid}, sorted by invoke_id.
  • Statifier.Session.start_link/2's :inherit_observers option (default false) starts every child a session starts for an <invoke> with that session's :trace setting and subscriber pids, plus inherit_observers: true of its own, so one opt-in at the root traces the whole invoke tree transitively.
  • Statifier.Chart.to_binary/1 and from_binary/1 give a compiled chart a versioned binary contract that carries its SCXML source and recompiles on load, never a compiled term. Statifier.Machine.source/1 and compile_opts/1 expose the source and persisted compile options a Machine was built from.
  • Statifier.Position.to_binary/1 and from_binary/2 give a MachineState a versioned binary contract that refuses to load against a chart whose identity does not match. Statifier.Position.export/1 and import/2 let a host migrate a position across chart revisions using string state ids.
  • Statifier.Session.start_link/2 gains a :resume option that boots a session at a persisted Statifier.Position instead of running Statifier.Interpreter.initialize/2. In-flight delayed-send timers, live invoked children, and the external inbox are not restored; see docs/persistence.md for why and for the host's remaining obligations.
  • Statifier.Session.start_link/2 accepts record: true to capture every delivered event, timer firing, cancel marker, and interpret/2 batch a session handles, in input order. Statifier.Session.recording/1 returns the captured Statifier.Session.Recording.t(), or {:error, :not_recording} if the session was not started with record: true.
  • Statifier.Replay.run/1 replays a Statifier.Session.Recording.t() through the pure core with no process and no timer, reproducing the original run's effect stream and terminal %Statifier.MachineState{}.
  • Statifier.Session.Recording.to_binary/1 and from_binary/1 give a recording a versioned binary contract that nests the chart's own blob and recompiles it on load, never a compiled term.
  • Statifier.Session.subscribe/3 with catch_up: true returns the session's recording alongside the subscription, so a pid that attached after start_link/2 can rebuild the effects it missed with Statifier.Replay.run/1. Requires record: true; otherwise returns {:error, :not_recorded} and does not subscribe.
  • Statifier.MachineState.new/2 accepts :max_macrostep_rounds (a positive integer, default 10_000, or :infinity), bounding one macrostep's fold.
  • A trace of a macrostep that never reaches quiescence can be ordered and counted: %Statifier.MachineState{} carries a round counter, and the Statifier.Effect.Trace.* payloads, %Statifier.Event.Cause{}, and %Statifier.Effect.BudgetExhausted{} are stamped with it.
  • Statifier.Session emits :telemetry events for its effect and trace stream - lifecycle, macrostep spans, and per-effect events, all prefixed [:statifier, :session, ...]. Statifier.Session.Telemetry.events/0 enumerates every event name a session can emit, and its moduledoc is the full measurement/metadata reference.
  • Statifier.Effect.DatamodelInit joins the core effect vocabulary as {:datamodel_init, %Statifier.Effect.DatamodelInit{}}, carrying the datamodel's starting map, and Statifier.Effect.DatamodelChange joins it as {:datamodel_change, %Statifier.Effect.DatamodelChange{}}; both have matching [:statifier, :session, :effect, ...] telemetry events. Every <data> binding - early or late - emits a :datamodel_change naming a d_index field, the <data> element's own compiler-assigned identity (mutually exclusive with the existing c_index); a failed or environment-overridden binding emits nothing, matching the write-side rule, and metadata.location for a binding resolves through Statifier.Machine.data/2.
  • The starting datamodel is fully reconstructable from the effect stream alone, with no Session.snapshot/1 call and no %Machine{} handle: a subscriber folds the one :datamodel_init baseline and every :datamodel_change after it and reproduces every datamodel key exactly, except "_event" (spec 5.10's current-event-under-evaluation, not authored or assigned content). This holds under both binding="early" and binding="late", for an environment-supplied :datamodel option (including one that shadows a top-level <data>), and with trace: false
    • both effects are core.
  • Statifier.Effect.Trace.EntrySet and Statifier.Effect.Trace.ExitSet gain a configuration field: the full configuration (ancestors included) as it stands after the entry set or exit set the payload names has been applied, so a subscriber can render the active configuration after every microstep without folding indexes deltas itself. At Statifier.Interpreter.exit_interpreter/1's termination sweep, ExitSet.configuration is the empty set; Trace.Done.configuration still carries the configuration as it stood at exit.
  • Statifier.Effect.SendDelayed and Statifier.Effect.Cancel gain an ordinal field: a per-execution sequence number minted from Statifier.MachineState's session-global timer_counter, which starts at 0 and never resets. It is what disambiguates two <foreach> iterations that execute the same <send delay> or <cancel> content node, in the same microstep, under the same author-written id - a durable host's at-least-once dedup key reads {session scope, send_id, macrostep, microstep, round, c_index, owner, ordinal}. [:statifier, :session, :effect, :send_delayed] and [..., :cancel] both carry ordinal in their telemetry measurements.
  • Statifier.Event, Statifier.Effect.SendDelayed, and Statifier.Effect.Cancel gain an opaque caller_context :: term() field (default nil, ADR-0063). A host attaches a correlation value - an OTel span context, a request id - via Statifier.Event.external(name, caller_context: ctx); the library copies it onto the durable-timer effects and onto the events a scheduled timer later delivers, and never reads it. Four telemetry events carry a caller_context metadata key: [:statifier, :session, :macrostep, :start] and [..., :stop] (the triggering external event's slot, nil for the other triggers), and [:statifier, :session, :effect, :send_delayed] and [..., :cancel] (the effect's own slot).
  • Statifier.Testing.Case and Statifier.Testing.FeatureDetector are public API. A downstream application can write declarative chart tests - an expected initial configuration, then {event, expected_configuration} steps - with use Statifier.Testing.Case, without copying files out of this repository. Documents using an unsupported SCXML feature flunk naming the feature rather than skipping. test_scxml/4 accepts optional :settle_window_ms and :configuration_deadline_ms for charts whose load-bearing delays exceed the defaults. See docs/testing-charts.md.
  • Statifier.Testing.HandlerCase: a reusable conformance case any Statifier.Invoke.Handler implementation runs against itself - planning purity and determinism, perform/2 idempotency on invoke_id, cancel semantics for unknown invocations, error.execution surfacing for a failing start/2, and exception propagation.

Changed

  • Expression syntax follows predicator 9.0, and the predicator dependency floor is ~> 9.0 (v1 shipped against predicator 2.x). if, else, while, undefined, and null are reserved words: using one as a bare identifier, as a property name after ., or as an unquoted object key is a compile error; quote an object key ({"null": 1}) to keep it. A bare undefined is the undefined literal and a bare null the null literal rather than variable loads. A declared datamodel entry with no value reads as undefined, not null, so x === undefined keeps its answer. Math.pow and Math.sqrt return an integer, not a float, when both the base/radicand and the result are integer-exact (Math.pow(2, 3) is 8, not 8.0); a float argument or a non-integer result is unaffected.
  • Statifier.Event's data default moves from nil to :undefined, and an environment-supplied :datamodel value of nil now means predicator's null rather than "declared, no value yet" - pass :undefined for that.
  • Statifier.Validator.validate/2 returns a third element, a list of non-fatal warnings, on both its {:ok, ...} and {:error, ...} arms.
  • Rejects an SCXML document whose root is missing xmlns or version, or whose version is not "1.0". v1 silently inserted both into the source and accepted any version.
  • Source locations always refer to the binary you passed in, whether or not it has an xmlns, a version, or an XML declaration. v1's relaxed mode rewrote the source and shifted reported positions; v2 never rewrites, so a span slices back out of your own string.
  • <log expr> is evaluated against the datamodel; a failed evaluation raises error.execution instead of logging nil.
  • <donedata><content> evaluates: an expr attribute is evaluated against the datamodel and its value coerced through Statifier.EventData.coerce/1; a text body is likewise coerced (so <content>21</content> yields the integer 21, not the string "21"). A failed expr raises error.execution and the done.state.* / terminal Effect.Done event carries no data, matching spec 5.10.1's "leave the field blank" rather than 5.6's empty-string rule for <content> in other contexts.
  • <param> under <donedata> compiles and evaluates: each <param>'s expr or location attribute compiles through the same value-expression path, and at done time the params fold in document order into a string-keyed map via Statifier.EventData.coerce({:params, _}) - a duplicate name takes the last value, and a <donedata> with no surviving params carries nil data, not %{}. A param whose expression fails to evaluate raises its own error.execution and is dropped from the result rather than aborting the remaining params (spec 5.7's "MUST ignore the name and value"). The done.state.* event and the terminal Effect.Done both carry the same folded map, and each such error.execution carries a {:donedata_param, state_index, param_index} cause origin naming the individual <param> that failed. A <donedata> must not specify both a <content> child and one or more <param> children (spec 5.5), and each <param> must specify exactly one of expr or location (spec 5.7).
  • The core {:done, %Statifier.Effect.Done{}} effect carries configuration, the full configuration as it stood at exit, so a consumer can observe the terminal position without switching tracing on.
  • Statifier.Session is restart: :temporary (v1's supervisor-friendly restart posture does not carry over). A supervisor restart would mint a fresh sess_ id and lose the crashed session's state, so restarting was never recoverable.
  • With tracing on, an empty executable-content block (an <onentry/> or <onexit/> with no children) emits a Trace.ContentExecuted effect with c_indexes: [], so a trace consumer can tell it ran with no content apart from a block that never ran at all. Untraced runs are unaffected.
  • A <send> or <invoke> whose namelist attribute contains a syntactically ill-formed location expression now loads successfully through Statifier.compile/1 instead of failing the whole document. The malformed entry is caught when the element runs: a <send> discards the message and an <invoke> aborts, each raising error.execution, per ADR-0036 and ADR-0031.
  • Statifier.Session.Recording blobs are format version 2, carrying an optional anchor position for recordings started by a resumed session; version 1 blobs still decode.
  • Hand-built %Statifier.Effect.SendDelayed{} and %Statifier.Effect.Cancel{} structs - test fixtures and durable-scheduler harnesses, mostly - must pass ordinal; it is enforced on both structs. Pattern matching on either struct is unaffected. Where a hand-built effect only needs to be distinct from its neighbours, any positive integer will do; where it stands in for one the engine produced, use the ordinal the engine stamped.

Removed

  • Statifier.Parser.parse/1 takes no options. v1's parse/2 had :relaxed (default true) and :xml_declaration (default false); v2 behaves as v1 did with relaxed: true, xml_declaration: false always, so callers relying on either option just drop it. A missing SCXML namespace or version is reported by validation.
  • Drops the uxid dependency. Session ids keep the same sess_-prefixed, hyphen-free, time-sortable format, so nothing that reads _sessionid needs to change.

Fixed

  • Character data is folded per XML 1.0 2.11: a literal CRLF pair or a lone literal CR in a <script>, <content>, <data>, or <assign> text body becomes a single \n, while a \r decoded from a character reference such as &#13; keeps its character. Script.text, Content.text, Data.text, and Assign.text now match what a conforming XML processor hands the engine on a CRLF checkout.
  • Attribute values are normalized per XML 1.0 3.3.3: a literal tab, newline, or carriage return inside an attribute value becomes a space, while a character reference such as &#10; keeps its character. A cond or expr wrapped across source lines now compiles from a single-line string.
  • A <send> whose type is unsupported, or whose target names none of the special targets C.1 defines, is rejected while the element is evaluated rather than after its block has run. The rest of the enclosing <onentry>/<onexit>/transition block no longer executes, per spec 4.9, and the raised error.execution carries the failing send's sendid.
  • A <send> whose target names a session, parent, or invocation that is not reachable raises error.communication at the <send>'s own position, carrying its sendid, and the rest of the enclosing block does not run (spec 4.9, C.1). A target that becomes unreachable after the block has run still raises the error afterwards.
  • <scxml initial="..."> naming a state nested under a wrapper state no longer fails validation. The document enters that state together with every ancestor between it and the root, per spec 3.11.
  • A macrostep that cannot reach quiescence returns a {:budget_exhausted, %Statifier.Effect.BudgetExhausted{}} effect with a resumable position instead of hanging the calling process.
  • Statifier.Position.to_binary/1 no longer writes routes or invoke_types into the blob, and Statifier.Position.from_binary/2 now blanks both fields to nil on decode regardless of what the blob carries. A resumed position previously came back with the stale per-drive snapshot from whenever it was saved; a host must re-stamp both before the first drive, as Statifier.Interpreter's moduledoc instructs.
  • A session reaching its top-level final (or being cancelled) discards its own pending delayed sends at the halt, per spec 6.2, instead of leaving the timers armed on the idled process - previously a delayed <send> scheduled before termination still fired and delivered to a live cross-session target. :budget_exhausted keeps its timers armed until a later cancel/1, since the interpreter has not exited there.

[1.9.0] 2025-09-09

Added

Enhanced Data Model Support

  • Statifier.active_leaf_states/1: Added public API function to retrieve only leaf states from active configuration
    • Leaf State Focus: Returns only the leaf (atomic) states that are currently active, excluding ancestor states
    • MapSet Return: Returns active leaf states as a MapSet for efficient membership testing and set operations
    • Public API: Provides direct access to leaf state information for client applications
    • Documentation: Comprehensive function documentation with clear usage examples

Enhanced Event Processing Improvements

  • SCXML-Compliant Event Matching: Complete implementation of W3C SCXML event matching patterns

    • Universal Wildcard: "*" matches any event name per SCXML specification
    • Prefix Matching: "foo" matches "foo", "foo.bar", "foo.bar.baz" with dot-separated token logic
    • OR Pattern Support: "foo bar" matches events that match "foo" OR "bar" (space-separated alternatives)
    • Wildcard Suffix: "foo.*" matches "foo.bar", "foo.baz" but not "foo" (requires additional tokens)
    • Token-Based Logic: Proper dot-separated token parsing for hierarchical event names
  • Error Event Generation: Comprehensive error.execution event generation per SCXML specification

    • Assign Action Errors: Failed assignments now generate error.execution events with detailed context
    • Error Event Structure: Events include reason, type, location, and expression information
    • Internal Event Queue: Error events properly queued as internal events for processing
    • Graceful Error Handling: State machine continues execution after logging errors

Strict Nested Assignment Validation

  • Enhanced Datamodel Validation: Strict checking for nested map assignments to prevent auto-creation
    • Intermediate Structure Validation: Assignments to nested paths require all intermediate structures to exist
    • Type Safety: Prevents assignment to non-map intermediate values with clear error messages
    • SCXML Compliance: Aligns with proper SCXML datamodel semantics for assignment operations
    • Error Reporting: Detailed error messages indicating specific validation failures

Changed

Test Infrastructure Improvements

  • Updated Internal Tests: Modified 9 internal tests to expect strict nested assignment behavior
    • Correct SCXML Behavior: Tests now verify proper failure when attempting to assign to non-existent intermediate structures
    • Error Expectation: Tests properly expect {:error, reason} responses for invalid assignments
    • Maintained Coverage: All test updates preserve comprehensive test coverage

Event Processing Updates

  • Enhanced Event Module: Improved event matching capabilities with comprehensive pattern support
    • Robust Pattern Matching: Handles complex event patterns with proper token parsing
    • Performance Optimization: Efficient string splitting and token comparison algorithms
    • Comprehensive Testing: Full test coverage for all event matching scenarios

Fixed

Code Quality Improvements

  • Removed Unnecessary Validation: Eliminated whitespace validation from Evaluator resolve_location functions
    • Simplified Logic: Removed redundant whitespace checking that wasn't addressing root issues
    • Cleaner Implementation: Focus on core location resolution functionality without extra validation layers
    • Performance: Reduced unnecessary string processing in location resolution

Datamodel Assignment Fixes

  • Strict Assignment Implementation: Fixed auto-creation of intermediate map structures in nested assignments
    • Prevented Invalid Behavior: No longer auto-creates intermediate maps when assigning to nested paths
    • Proper Error Handling: Clear error messages when attempting to assign to non-existent intermediate structures
    • SCXML Compliance: Aligns with W3C SCXML specification for datamodel assignment semantics

Technical Improvements

Enhanced Test Coverage

  • Comprehensive Event Testing: Added extensive tests for SCXML event matching patterns
    • Universal Wildcard Tests: Verification that "*" matches all event types
    • Prefix Pattern Tests: Testing hierarchical event matching with dot notation
    • OR Logic Tests: Validation of space-separated alternative event patterns
    • Wildcard Suffix Tests: Complex wildcard pattern testing with proper token requirements
    • Coverage Achievement: Improved test coverage to 90.1% (up from 89.7%)

Developer Experience

  • Enhanced Error Messages: Improved error context and logging throughout assignment operations
  • Structured Logging: Comprehensive logging with metadata for debugging assignment failures
  • Debug Support: Enhanced debugging capabilities with elixir log adapter recommendations

Examples

Event Matching Patterns

<!-- Universal wildcard - matches any event -->
<transition event="*" target="catch_all"/>

<!-- Prefix matching - matches "user", "user.login", "user.logout" -->
<transition event="user" target="user_handler"/>

<!-- OR patterns - matches "start" OR "begin" OR "init" -->
<transition event="start begin init" target="startup"/>

<!-- Wildcard suffix - matches "system.error", "system.warning" but not "system" -->
<transition event="system.*" target="system_handler"/>

Error Event Handling

<state id="processing">
  <onentry>
    <!-- This will generate error.execution event if foo doesn't exist -->
    <assign location="foo.bar" expr="'value'"/>
  </onentry>
  
  <!-- Handle assignment errors -->
  <transition event="error.execution" target="error_state">
    <log expr="'Assignment failed: ' + _event.data.reason"/>
  </transition>
</state>

Strict Assignment Validation

# This will now fail with proper error instead of auto-creating structures
{:error, reason} = Datamodel.put_in_path(%{}, ["foo", "bar"], "value")
# reason: "Cannot assign to nested path: 'foo' does not exist"

# Proper usage requires intermediate structures to exist
datamodel = %{"foo" => %{}}
{:ok, updated} = Datamodel.put_in_path(datamodel, ["foo", "bar"], "value")
# updated: %{"foo" => %{"bar" => "value"}}

Migration Notes

  • Event Matching: Existing event patterns continue to work with enhanced capabilities
  • Assignment Behavior: Code relying on auto-creation of intermediate structures may need updates
  • Error Handling: New error.execution events provide better error visibility and handling
  • Test Coverage: Internal tests updated to reflect correct SCXML assignment behavior

Notes

  • Enhanced SCXML Compliance: Improved adherence to W3C SCXML specification for event processing and datamodel operations
  • Better Error Handling: Comprehensive error event generation and structured error reporting
  • Robust Event Processing: Full implementation of SCXML event matching patterns with proper token-based logic
  • Strict Datamodel Semantics: Proper validation of nested assignments prevents unexpected behavior
  • Test Coverage Improvement: Achieved 90.1% test coverage with comprehensive event matching tests

[1.8.0] 2025-09-02

Added

Documentation Site and Infrastructure

  • VitePress Documentation Site: Complete documentation site setup with Diataxis structure following the four documentation types (Tutorials, How-to Guides, Reference, Explanation)
  • GitHub Actions Integration: Automatic deployment to GitHub Pages with separate workflows for documentation building and linting
  • Specialized Documentation Agent: Added Diataxis-aware documentation agent for structured content creation and management

SCXML Feature Enhancements

  • Internal Transitions: Complete implementation of type="internal" transitions that execute actions without exiting/re-entering source state per W3C SCXML specification
  • Enhanced Send Elements: Improved <send> element support with better content data processing, parameter validation, and JSON serialization for complex values
  • Nested If Parsing: Fixed SAX parser to properly handle nested <if> blocks, enabling complex conditional logic structures

Performance and Developer Experience

  • Macro-Based Logging: Converted all LogManager logging functions to performance-optimized macros with lazy evaluation and zero overhead when logging is disabled
  • Environment-Aware Logging: Added automatic logging configuration based on environment (trace in dev, debug in test, info in prod)
  • Enhanced Debugging: Comprehensive trace logging throughout transition resolution and action execution with structured metadata

Fixed

  • Parallel Transition Conflicts: Enhanced TransitionResolver to handle conflicts between transitions from parallel regions using document order per SCXML specification
  • Expression Compilation: Moved expression compilation from creation-time to validation-time with fallback runtime compilation for backward compatibility
  • Pipeline-Friendly APIs: Updated all action execute functions to take state_chart as first argument for better Elixir pipeline composition

Changed

  • Feature Detection Updates: Moved send_content_elements and send_param_elements from partial to supported status
  • Code Quality: Resolved Credo static analysis issues by extracting helper functions and reducing cyclomatic complexity
  • Error Handling: Improved error context logging for failed conditions and expression evaluations

Infrastructure

  • Separated Workflows: Split documentation workflows into focused build/deployment and linting processes
  • ESM Module Support: Added proper ES module configuration for VitePress compatibility
  • Test Coverage: Improved test coverage across parser components, action executors, and logging infrastructure

[1.7.0] 2025-09-01

Added

Enhanced SCXML Feature Detection and Test Infrastructure

  • Comprehensive Feature Detection: Added detection for 8 new SCXML features including wildcard_events, invoke_elements, script_elements, cancel_elements, finalize_elements, donedata_elements, send_content_elements, send_param_elements, and send_delay_expressions
  • Automated Test Updates: Created script to automatically update @required_features attributes across 182 test files (123 SCION + 59 W3C) based on actual XML content analysis
  • Wildcard Events Support: Full implementation of event="*" patterns with proper transition processing and comprehensive test coverage
  • Partial Feature Testing: Modified test framework to allow :partial features to run, providing better feedback instead of automatic exclusion

New SCXML Elements and Features

  • Foreach Element Support: Complete SCXML <foreach> implementation with W3C-compliant variable scoping, permanent variable declaration, and nested action support
  • Targetless Transitions: Implementation of SCXML targetless transitions that execute actions without state changes, following W3C specification requirements
  • Enhanced Send Elements: Improved <send> element parsing with proper content element text capture, fixing previously ignored text content in send actions

Development Infrastructure

  • Quality Mix Task: Added comprehensive mix quality task with automated formatting, testing, static analysis, and coverage checking
  • Coverage Improvements: Significantly improved test coverage across multiple modules including parser components, action executors, and logging infrastructure

Changed

Test Framework Improvements

  • Enhanced Feature Validation: Updated FeatureDetector.validate_features/1 to treat :partial features as runnable rather than excluded
  • Improved Test Accuracy: All test files now have precise feature requirements based on actual SCXML content rather than manual specification
  • Better Regression Coverage: Regression test coverage improved from 141/142 to 145/145 (100% pass rate)

SCXML Compliance Enhancements

  • History State Fixes: Fixed history state restoration to properly execute ancestor onentry actions per W3C specification
  • Logging Improvements: Implemented safe_to_string function to handle complex data types in log actions, preventing String.Chars protocol errors
  • Increased Iteration Limits: Raised eventless transition iteration limit from 100 to 1000 to handle complex automatic transition chains

Fixed

  • Content Element Parsing: Fixed SAX parser to capture text content within <content> elements for send actions
  • Variable Scoping: Proper SCXML variable scoping in foreach loops with restoration of existing variables after iteration
  • Feature Classification: Corrected wildcard_events status from :partial to :supported with full implementation

Benefits

  • Enhanced Test Coverage: Comprehensive detection prevents false positive/negative test results with accurate feature requirements
  • Better Development Feedback: Partial features now provide real feedback rather than being automatically excluded from testing
  • SCXML Compliance: Improved adherence to W3C SCXML specification with proper implementation of complex features like foreach and targetless transitions
  • Developer Experience: Automated quality checking and enhanced test infrastructure provide better development workflow

All 857+ tests continue to pass with enhanced regression coverage and improved SCXML feature support.

[1.6.0] 2025-08-30

Changed

API Consolidation and Cleanup

  • Consolidated Active States API: Unified active states functionality into single source of truth

    • Renamed Functions for Clarity: active_statesactive_leaf_states, active_ancestorsall_active_states
    • Single Source of Truth: All active state queries now handled by Configuration module
    • Removed Wrapper Functions: Eliminated duplicate functions from StateChart and Interpreter modules
    • Updated All Tests: All 857 tests updated to use consolidated API directly
    • Fixed History Tracking: History tracking now correctly uses all active states (including ancestors) for proper shallow history computation
  • Removed Backwards Compatibility Layers: Cleaned up legacy API functions for better maintainability

    • Removed Legacy Delegates: Eliminated Statifier.validate/1 and Statifier.interpret/1 delegate functions
    • Removed Statifier.parse_only/1: Eliminated unused function that provided no additional value over SCXML.parse/2
    • Forced Explicit Module Usage: Users must now call Statifier.Interpreter.initialize/1 and Statifier.Validator.validate/1 directly
    • Updated Documentation: All examples and README updated to use explicit module references

Code Quality Improvements

  • Implemented Proper Logging: Replaced TODO comments with actual logging infrastructure

    • Validation Warning Logging: Interpreter.initialize/1 now properly logs validation warnings using LogManager
    • Structured Logging: Warnings logged with warning count and detailed messages
    • Consistent Infrastructure: Uses existing logging system throughout codebase
  • Fixed All Credo Issues: Resolved all static code analysis warnings

    • Added Module Aliases: Proper module aliasing to eliminate nested module access warnings
    • Clean Code Standards: All 288 source files now pass mix credo --strict with no issues
    • Improved Readability: Better import organization and alias usage

Benefits

  • Clearer API: Eliminates confusion between multiple similar functions
  • Better Maintainability: Single source of truth for active state management
  • Explicit Architecture: Direct module usage removes API ambiguity
  • Enhanced Debugging: Proper structured logging for validation issues
  • Code Quality: Clean codebase with no static analysis issues

All 857 tests pass with 91.2% code coverage maintained throughout the refactoring.

[1.5.0] 2025-08-29

Added

Modern API with Relaxed Parsing Mode

  • Statifier.parse/2 Function: New streamlined API combining parsing and validation in one call

    • 3-Tuple Return Format: Returns {:ok, document, warnings} for comprehensive result handling
    • Automatic Validation: Validates documents by default, returns errors as {:error, {:validation_errors, errors, warnings}}
    • Options Support: Accepts keyword options for parsing customization
    • Relaxed Mode Support: Passes options to SCXML.parse for enhanced flexibility
    • Skip Validation Option: validate: false returns unvalidated documents for advanced use cases
  • Enhanced SCXML.parse/2 with XML Normalization: Comprehensive relaxed parsing mode for simplified SCXML authoring

    • XML Declaration Handling: Optional XML declaration addition with xml_declaration option (default: false to preserve line numbers)
    • Default Namespace Addition: Automatically adds W3C SCXML namespace when missing
    • Default Version Addition: Automatically adds version="1.0" when missing
    • Backwards Compatible: Preserves existing XML declarations and attributes when present
    • Test-Friendly: Eliminates XML boilerplate for cleaner test documents
  • Validation Status Tracking: Added validated field to Document struct for better API clarity

    • Document.validated: Boolean field indicating whether document has been validated
    • Interpreter Optimization: Skips redundant validation for pre-validated documents
    • Helper Function: Statifier.validated?/1 for checking document validation status

Basic Send Element Support

  • <send> Element Implementation: Comprehensive Phase 1 support for SCXML send elements with internal event communication

    • Statifier.Actions.SendAction: Complete data structure with event_expr, target_expr, type_expr, delay_expr, namelist support
    • Statifier.Actions.SendParam: Support for <param> child elements with name/expr attributes
    • Statifier.Actions.SendContent: Support for <content> child elements with expr attribute
    • Expression Evaluation: Dynamic event names, target resolution, and data payload construction
    • Internal Event Routing: Events sent to #_internal properly queued and processed in state machine
    • Transition Actions: Send elements within <transition> elements with proper execution order
  • Enhanced Parser Support: Extended SCXML parser for comprehensive send element parsing

    • Send Element Parsing: Complete parsing of <send> elements with all W3C attributes
    • Child Element Support: Parsing of nested <param> and <content> elements
    • Location Tracking: Precise source location tracking for all send-related elements
    • Handler Integration: SAX-based parsing with proper state stack management
  • ActionExecutor Integration: Enhanced action execution framework with send support

    • Transition Action Execution: Added execute_transition_actions/3 for actions within transitions
    • Proper Action Order: SCXML-compliant action execution (exit → transition → entry)
    • Pipeline Programming: Refactored parameter order for better |> operator usage

StateHierarchy Module Extraction

  • Statifier.StateHierarchy: 422-line dedicated module extracted from Interpreter for hierarchy operations
    • 8 Core Functions: descendant_of?/3, compute_lcca/3, get_ancestor_path/2, get_parallel_ancestors/2, etc.
    • Reduced Interpreter Size: 824 → 636 lines (23% reduction, 188 lines extracted)
    • Single Responsibility: All state hierarchy logic consolidated in focused module
    • Comprehensive Testing: 45 new tests covering complex hierarchies, edge cases, parallel regions

Hierarchy Caching Infrastructure

  • Statifier.HierarchyCache: O(1) performance optimization system for expensive hierarchy operations

    • Pre-computed Relationships: Ancestor paths, LCCA matrix, descendant sets, parallel regions
    • Performance Gains: 5-15x speedup for hierarchy operations (O(depth) → O(1))
    • Memory Efficient: ~1.5-2x memory overhead for significant performance benefits
    • Automatic Building: Cache built during validation phase for valid documents only
    • Statistics Tracking: Build time, memory usage, and cache size metrics
  • Enhanced Document Structure: Extended Document struct with hierarchy_cache field

    • Integration with Validation: Cache built in Validator.finalize/2 pipeline
    • Helper Functions: Document.get_all_states/1 for comprehensive state enumeration
    • Benchmark Testing: Performance and memory usage validation with dedicated benchmarks

TransitionResolver Module Extraction

  • Statifier.Interpreter.TransitionResolver: 161-line focused module extracted from Interpreter
    • Single Responsibility: Dedicated to SCXML transition conflict resolution and matching
    • 6 Core Functions: find_enabled_transitions/2, find_eventless_transitions/1, resolve_transition_conflicts/2, etc.
    • SCXML-Compliant: Implements W3C specification for optimal transition set computation
    • Comprehensive Testing: 300 lines of tests with 12 test cases covering all scenarios
    • Better Maintainability: Reduces Interpreter complexity from 655 to 581 lines (11% reduction)

Changed

API Modernization and Backwards Compatibility

  • ⚠️ BREAKING: Updated all test files to use new 3-tuple Statifier.parse/2 API

    • Comprehensive Migration: All 857 tests updated to new API format
    • Maintained Coverage: All tests continue passing with enhanced API
    • Improved Test Clarity: 3-tuple format provides better access to warnings in tests
  • Streamlined Main Module: Complete rewrite of /lib/statifier.ex with modern architecture

    • New Functions: parse/2 and validated?/1 for comprehensive API coverage
    • Error Handling: Enhanced error handling with handle_validation/2 helper
    • Reduced Nesting: Improved code maintainability with better function organization
    • Options Integration: Seamless integration with relaxed parsing options

Code Quality and Performance Improvements

  • Perfect Credo Compliance: Achieved 0 issues across 863 analyzed modules/functions

    • Function Nesting Depth: Fixed all nesting depth violations in StateHierarchy module
    • Helper Function Extraction: Added check_descendant_relationship/3, lookup_lcca_in_matrix/3, normalize_lcca_key/2
    • Clean Architecture: Better separation of concerns and improved readability
    • Benchmark Test Configuration: Added Credo disable for IO.puts in benchmark tests
  • Major Interpreter Refactoring: Comprehensive architectural improvements for better maintainability

    • Module Extraction Benefits: StateHierarchy, TransitionResolver, and HierarchyCache provide focused functionality
    • Performance Optimizations: O(1) hierarchy operations with pre-computed cache infrastructure
    • Pipeline Programming: Enhanced parameter ordering for better Elixir |> operator usage
    • Action Execution Improvements: Proper SCXML-compliant action execution order and integration
    • Future Extensibility: Clean architecture prepared for advanced SCXML features and optimizations

Enhanced Action Execution Architecture

  • ActionExecutor Parameter Refactoring: Improved parameter ordering for better Elixir programming patterns
    • StateChart First: All execute_*_actions functions now put state_chart as first parameter
    • Pipeline Friendly: Better |> operator support for functional programming style
    • Transition Actions: New execute_transition_actions/3 function for actions within transitions
    • Separation of Concerns: Moved transition action execution from Interpreter to ActionExecutor

Fixed

XML Normalization and Location Tracking

  • Version Attribute Detection: Fixed regex pattern in maybe_add_default_version/1 for proper version attribute recognition
  • Location Tracking Preservation: Ensured line number accuracy maintained with optional XML declaration
  • Function Signature Conflicts: Resolved parse/1 vs parse/2 function definition conflicts

Test Infrastructure Improvements

  • Location Tracking Tests: Updated location-specific tests to include XML declarations for accurate line numbers
  • TransitionResolver Integration: Fixed StateChart field name issues and event timing in extracted module tests
  • Comprehensive Test Coverage: All 857 tests passing with new architecture and API changes

Technical Improvements

Enhanced Developer Experience

  • Simplified SCXML Authoring: Relaxed parsing mode eliminates repetitive XML boilerplate

    • No XML Declaration Required: Tests can omit <?xml version="1.0" encoding="UTF-8"?>
    • No Namespace Required: Automatic W3C SCXML namespace addition
    • No Version Required: Automatic version="1.0" addition
    • Cleaner Test Documents: Focus on state machine logic rather than XML syntax
  • Better Error Messages: Enhanced validation error reporting with maintained source location accuracy

  • Improved API Consistency: Uniform return patterns and option handling across all parsing functions

  • Comprehensive Documentation: Updated all function documentation with examples and options

Performance and Quality Metrics

  • All Quality Gates Pass: Format ✓ Test (857/857) ✓ Credo (0 issues) ✓ Dialyzer ✓
  • Comprehensive Test Coverage: 857 total tests with significant new module coverage
    • New Test Modules: SendAction (236 lines), StateHierarchy (591 lines), TransitionResolver (313 lines)
    • Advanced Testing: Handler (483 lines), StateStack (453 lines), HierarchyCache (524 lines)
    • Performance Benchmarks: HierarchyCache benchmarks demonstrate 5-15x performance improvements
  • Memory Efficiency: O(1) hierarchy operations with intelligent caching system
  • Production Ready: All functionality thoroughly tested with comprehensive edge case coverage
  • Architecture Quality: Clean separation of concerns with focused, testable modules

Examples

New Streamlined API

# Modern API - Parse and validate in one step
{:ok, document, warnings} = Statifier.parse(xml)

# Parse without validation for advanced use cases  
{:ok, document, []} = Statifier.parse(xml, validate: false)

# Check validation status
validated = Statifier.validated?(document)  # true/false

# Skip validation explicitly
{:ok, document, []} = Statifier.parse(xml, validate: false)

Relaxed XML Parsing Mode

# Before v1.5.0 - Full XML boilerplate required
xml = """
<?xml version="1.0" encoding="UTF-8"?>
<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" initial="start">
  <state id="start"/>
</scxml>
"""

# After v1.5.0 - Clean, minimal syntax
xml = """
<scxml initial="start">
  <state id="start"/>
</scxml>
"""

{:ok, document, warnings} = Statifier.parse(xml)
# XML declaration, namespace, and version automatically added

XML Declaration Control

# Preserve line numbers (default behavior)
{:ok, document, warnings} = Statifier.parse(minimal_xml)

# Add XML declaration explicitly
{:ok, document, warnings} = Statifier.parse(minimal_xml, xml_declaration: true)

Error Handling Examples

# Validation errors with enhanced format
case Statifier.parse(invalid_xml) do
  {:ok, document, warnings} -> 
    # Success with optional warnings
  {:error, {:validation_errors, errors, warnings}} -> 
    # Validation failed with detailed errors
  {:error, reason} -> 
    # Parsing failed
end

Send Element Usage

<scxml initial="waiting">
  <state id="waiting">
    <transition event="start" target="processing">
      <!-- Send internal event with data -->
      <send target="#_internal" event="process_data">
        <param name="userId" expr="'user123'"/>
        <param name name="priority" expr="5"/>
        <content expr="'Processing started'"/>
      </send>
    </transition>
  </state>
  
  <state id="processing">
    <transition event="process_data" target="complete">
      <!-- Event data available via _event.data -->
      <log expr="'Processing for user: ' + _event.data.userId"/>
    </transition>
  </state>
  
  <state id="complete"/>
</scxml>

Dynamic Send Elements

<state id="router">
  <transition event="route_message">
    <!-- Dynamic event and target evaluation -->
    <send targetexpr="_event.data.target" 
          eventexpr="_event.data.eventName"
          namelist="status priority">
      <param name="timestamp" expr="Date.now()"/>
    </send>
  </transition>
</state>

Performance Optimization Examples

# Before v1.5.0 - O(depth) hierarchy operations
time_uncached = benchmark_hierarchy_operations(uncached_document)

# After v1.5.0 - O(1) hierarchy operations with caching
{:ok, cached_document, _warnings} = Statifier.parse(xml)
time_cached = benchmark_hierarchy_operations(cached_document)

# Typical performance improvement: 5-15x speedup
speedup = time_uncached / time_cached  # => ~10.5x

Migration Guide

API Updates

# Before v1.5.0
{:ok, document} = SCXML.parse(xml)
{:ok, validated_doc, warnings} = Validator.validate(document)

# After v1.5.0 - Streamlined approach
{:ok, document, warnings} = Statifier.parse(xml)

Test Simplification

# Before v1.5.0 - Verbose XML
xml = """
<?xml version="1.0" encoding="UTF-8"?>
<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" initial="idle">
  <state id="idle">
    <transition event="start" target="running"/>
  </state>
  <state id="running"/>
</scxml>
"""

# After v1.5.0 - Focus on logic
xml = """
<scxml initial="idle">
  <state id="idle">
    <transition event="start" target="running"/>
  </state>
  <state id="running"/>
</scxml>
"""

Notes

  • Major Release: Comprehensive modernization spanning API, architecture, performance, and new SCXML features
  • API Modernization: Complete modernization of parsing and validation API for better developer experience
  • Architectural Revolution: Major refactoring with StateHierarchy, TransitionResolver, HierarchyCache, and SendAction modules
  • Performance Breakthrough: O(1) hierarchy operations provide 5-15x performance improvements for complex state machines
  • SCXML Feature Expansion: Basic send element support enables internal event communication and transition actions
  • Quality Excellence: Perfect Credo compliance, comprehensive test coverage (857 tests), and thorough documentation
  • Developer Productivity: Significant reduction in XML boilerplate and improved error handling
  • Production Ready: Battle-tested architecture with comprehensive edge case coverage and benchmark validation
  • Foundation for Future: Clean, extensible architecture prepares for advanced SCXML features (delays, external targets, etc.)

[1.4.0] 2025-08-29

Added

Complete SCXML History State Support

  • History State Data Model: Full support for SCXML <history> elements per W3C specification

    • Statifier.State Extensions: Added history_type field (:shallow | :deep) and history_type_location for validation

    • Parser Support: Complete parsing of <history> elements with type="shallow|deep" attributes
    • Default Behavior: History type defaults to :shallow when not specified
    • Element Builder: New build_history_state/4 function for creating history state structures
    • Location Tracking: Full source location tracking for history elements and attributes
  • History State Validation: Comprehensive validation per W3C SCXML specification requirements

    • Statifier.Validator.HistoryStateValidator: Dedicated validator module for all history constraints
    • Structural Validation: History states cannot be at root level (must have compound/parallel parent)
    • Content Validation: History states cannot have child states (pseudo-states only)
    • Uniqueness Validation: Only one history state per type (shallow/deep) per parent state
    • Type Validation: History type must be valid (:shallow or :deep)
    • Target Validation: Default transition targets must exist in document
    • Reachability Analysis: Warns if history states are unreachable (no transitions target them)
  • History Tracking Infrastructure: Complete W3C SCXML compliant history recording and restoration

    • Statifier.HistoryTracker: Core history state tracking with efficient MapSet operations
    • Shallow History: Records and restores immediate children of parent state that contain active descendants
    • Deep History: Records and restores all atomic descendant states within parent state
    • StateChart Integration: History tracking integrated into StateChart lifecycle
    • Record History API: record_history/2, get_shallow_history/2, get_deep_history/2, has_history?/2
  • History State Resolution: Full W3C SCXML compliant history state transition resolution

    • Pseudo-State Handling: History states resolve to stored configuration or default targets (never active themselves)
    • Shallow Resolution: Restores immediate children from recorded shallow history
    • Deep Resolution: Restores all atomic descendants from recorded deep history
    • Default Transitions: Uses history state's default transitions when parent has no recorded history
    • Complex Hierarchy Support: Maintains proper state hierarchy during restoration

Multiple Transition Target Support

  • Space-Separated Target Parsing: SCXML transitions now support multiple targets per W3C specification

    • Parser Enhancement: Handles target="state1 state2 state3" syntax with proper whitespace splitting
    • Data Model: Statifier.Transition.targets field (list) replaces target field (string)
    • Validator Updates: All transition validators updated for list-based target validation
    • Empty Target Support: Empty target lists properly handled for targetless transitions
    • Feature Detection: Updated feature detection to recognize multiple target capability
  • Enhanced Parallel State Exit Logic: Critical fix for W3C SCXML parallel state exit semantics

    • Exit Set Computation: Proper W3C SCXML exit set calculation for complex parallel hierarchies
    • Parallel Ancestor Detection: get_parallel_ancestors/3 identifies all parallel ancestors in hierarchy
    • Region Identification: are_in_parallel_regions/3 correctly identifies states in different parallel regions
    • Cross-Boundary Exits: exits_parallel_region/3 detects transitions that exit parallel regions
    • Comprehensive Exit Logic: All parallel regions properly exited when transitioning to external states

Changed

API Improvements (Breaking Changes)

  • ⚠️ BREAKING: Statifier.Transition struct field renamed from target to targets
    • Type Change: target: String.t() | niltargets: [String.t()]

    • Migration: Update pattern matches from %Transition{target: target} to %Transition{targets: targets}
    • Benefit: Self-documenting code that clearly indicates list-based target support
    • Validation: All existing tests and validators updated for new API

Document Helper Functions

  • Statifier.Document Enhancements: New helper functions for history state runtime management
    • is_history_state?/2: Check if state has history type with O(1) lookup
    • find_history_states/2: Find all history states within a parent state
    • get_history_default_targets/2: Get default transition targets for history state
    • Optimized Performance: All functions use existing O(1) state_lookup maps

History Integration in Interpreter

  • Interpreter History Support: Complete integration of history states into state machine lifecycle
    • History Recording: Automatic history recording before onexit actions during state transitions
    • W3C Timing Compliance: History recorded "before taking any transition that exits the parent"
    • Parent Detection: find_parents_with_history/2 identifies parents needing history recording
    • Ancestor Analysis: get_ancestors_with_history/2 finds all ancestors with history children
    • StateChart Parameters: Enhanced interpreter functions to work with StateChart for history access

Fixed

SCION Test Coverage Improvements

  • History Test Parsing: Fixed critical parser bug where transitions inside <history> elements weren't being processed

    • StateStack Fix: Added missing {"history", parent_state} case in handle_transition_end/1
    • History Default Transitions: History states can now have proper default transitions
    • SCION History Tests: 5/8 SCION history tests now passing (62.5% success rate, up from 12.5%)
    • Test Results: history0, history1, history2, history3, history6 now pass
  • Parallel State Exit Logic: Resolved critical parallel state exit semantics issues

    • Cross-Region Transitions: Fixed transitions from parallel regions to external states
    • Exit Set Calculation: Proper W3C SCXML exit set computation for complex hierarchies
    • SCION Test Fixes: Multiple SCION history tests (history4b, history5) now pass completely
    • Regression Protection: All 118 regression tests continue to pass

Feature Detection Updates

  • History State Support: Updated FeatureDetector to mark :history_states as :supported
  • Multiple Target Support: Enhanced feature detection for multiple transition targets
  • Test Infrastructure: 12 history state tests (8 SCION + 4 W3C) now properly validated

Technical Improvements

Test Infrastructure

  • Comprehensive Test Coverage: 707 total tests with enhanced history state coverage
    • New Test Organization: Created test/statifier/history/ folder for organized history testing
    • History Test Suite: 15+ dedicated history tests covering all scenarios (recording, restoration, validation)
    • Integration Tests: End-to-end testing of history states with complex state hierarchies
    • Regression Tests: All 118 regression tests continue passing with new functionality

Code Quality

  • Credo Compliance: All static analysis issues resolved across the codebase
  • Pattern Matching: Enhanced pattern matching for cleaner, more readable code
  • Type Safety: Full typespec coverage for all new history state functionality
  • Documentation: Comprehensive documentation for all new modules and functions
  • Performance: Maintained O(1) lookups with efficient MapSet operations for history tracking

W3C SCXML Compliance

  • History State Specification: Full compliance with W3C SCXML 1.0 history state requirements
  • Parallel State Semantics: Proper W3C exit set computation and parallel region handling
  • Multiple Target Support: Compliant with W3C SCXML multiple target syntax
  • Pseudo-State Handling: Correct implementation of history as non-active pseudo-states
  • Default Transition Logic: Proper handling of history default transitions per specification

Examples

History State Usage

<?xml version="1.0" encoding="UTF-8"?>
<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" initial="main">
  <state id="main" initial="sub1">
    <!-- Shallow history - restores immediate children -->
    <history id="main_hist" type="shallow">
      <transition target="sub1"/>  <!-- Default when no history -->
    </history>
    
    <state id="sub1">
      <transition event="go" target="sub2"/>
    </state>
    
    <state id="sub2">
      <transition event="go" target="sub3"/>
    </state>
    
    <state id="sub3">
      <transition event="exit" target="other"/>
      <transition event="back" target="main_hist"/>  <!-- Restore history -->
    </state>
  </state>
  
  <state id="other">
    <transition event="return" target="main_hist"/>  <!-- Restore to last sub-state -->
  </state>
</scxml>

Deep History Example

<parallel id="game">
  <!-- Deep history - restores all atomic descendants -->
  <history id="game_hist" type="deep">
    <transition target="level1"/>  <!-- Default: start at level 1 -->
  </history>
  
  <state id="progress" initial="level1">
    <state id="level1">
      <state id="checkpoint1"/>
      <state id="checkpoint2"/>
    </state>
    <state id="level2">
      <state id="checkpoint3"/>
      <state id="checkpoint4"/>
    </state>
  </state>
  
  <state id="inventory" initial="empty">
    <state id="empty"/>
    <state id="sword"/>
    <state id="shield"/>
  </state>
</parallel>

Multiple Target Transitions

<state id="source">
  <!-- Multiple targets - enter multiple states simultaneously -->
  <transition event="activate" target="target1 target2 target3"/>
</state>

<parallel id="system">
  <state id="target1"/>
  <state id="target2"/>  
  <state id="target3"/>
</parallel>

Programmatic History Usage

# Check if state is a history state
Document.is_history_state?(document, "main_hist")  # true

# Find all history states in a parent
history_states = Document.find_history_states(document, "main")

# Get default targets for history state
defaults = Document.get_history_default_targets(document, "main_hist")

# Record and retrieve history
state_chart = StateChart.record_history(state_chart, "main")
shallow_history = StateChart.get_shallow_history(state_chart, "main")
deep_history = StateChart.get_deep_history(state_chart, "main")

Migration Guide

Transition Target API

# Before v1.4.0
%Transition{target: "state1"}
%Transition{target: nil}  # targetless

# After v1.4.0  
%Transition{targets: ["state1"]}
%Transition{targets: []}  # targetless

# Pattern matching migration
case transition do
  %Transition{target: nil} -> # targetless
  %Transition{target: target} -> # has target
end

# becomes
case transition do
  %Transition{targets: []} -> # targetless
  %Transition{targets: targets} -> # has targets
end

Notes

  • History State Foundation: Complete foundation for SCXML history states established
  • W3C Compliance: Full compliance with W3C SCXML 1.0 history state specification
  • Multiple Target Support: Enhanced SCXML transition capability per specification
  • Parallel State Fixes: Critical parallel state exit logic issues resolved
  • Test Coverage: Comprehensive test coverage maintained (91.8% overall)
  • Production Ready: All functionality thoroughly tested and validated
  • SCION Progress: Significant improvement in SCION history test compliance

[1.3.0] 2025-08-27

Added

Core Logging Infrastructure

  • Flexible Protocol-Based Logging System: Complete logging architecture for state chart operations

    • Statifier.Logging.Adapter Protocol: Extensible logging backend interface with log/5 and enabled?/2 functions
    • Statifier.Logging.ElixirLoggerAdapter: Production logging adapter that integrates with Elixir's Logger system
    • Statifier.Logging.TestAdapter: In-memory log storage adapter for clean test environments
    • Statifier.Logging.LogManager: Central coordination module with automatic metadata extraction
    • Log Level Hierarchy: Complete support for :trace, :debug, :info, :warn, :error levels with filtering
  • Automatic Metadata Extraction: StateChart context automatically added to all log messages

    • Current State Tracking: Active states automatically included in log metadata
    • Event Context: Current event name automatically included when available
    • Custom Metadata Support: Additional metadata can be provided per log message
    • Metadata Precedence: Custom metadata takes precedence over automatic extraction
  • Advanced Memory Management: Circular buffer support for bounded log storage

    • Configurable Limits: TestAdapter supports optional max_entries for memory-bounded logging
    • Circular Buffer Behavior: Automatically removes oldest entries when limit exceeded
    • Unlimited Storage: Optional unlimited log storage for comprehensive test coverage
    • Helper Functions: get_logs/1,2, clear_logs/1 for test log inspection and management
  • StateChart Integration: Enhanced StateChart structure with logging capabilities

    • Logging Fields: Added log_adapter, log_level, and logs fields to StateChart struct
    • Configuration Helpers: configure_logging/3 and set_log_level/2 functions for easy setup
    • Seamless Integration: Logging works with existing StateChart lifecycle and event processing

Logging Configuration System

  • Enhanced Interpreter.initialize/2: Comprehensive logging configuration support during state chart initialization

    • Runtime Configuration Options: Accept :log_adapter and :log_level options via keyword list
    • Adapter Configuration Flexibility: Support for direct adapter structs or {Module, opts} tuples
    • Backward Compatibility: Existing initialize/1 calls continue to work with sensible defaults
    • Comprehensive Documentation: Detailed examples and usage patterns in function documentation
  • Centralized Configuration Logic: All configuration logic consolidated in LogManager.configure_from_options/2

    • Configuration Precedence: Runtime options > Application config > Environment defaults
    • Application Configuration Support: Integration with Application.get_env/3 for system-wide settings
    • Robust Error Handling: Graceful fallback to ElixirLoggerAdapter on invalid configurations
    • Validation and Safety: Comprehensive configuration validation with detailed error messages
  • Production-Ready Defaults: Sensible defaults that work across all environments

    • ElixirLoggerAdapter Default: Always the base default for robust logging in all environments
    • Test Environment Configuration: TestAdapter configured via test_helper.exs for clean test output
    • Flexible Fallback Strategy: Invalid configurations always fall back to most robust adapter
    • Environment Independence: No dependency on Mix.env() or custom environment detection
  • Comprehensive Configuration Testing: 12 dedicated tests covering all configuration scenarios

    • Runtime Configuration Tests: Verification of all option types and combinations
    • Application Configuration Tests: Testing precedence and override behavior
    • Error Handling Tests: Validation of graceful fallback for invalid configurations
    • Integration Tests: End-to-end testing of configuration system with state chart initialization

Examples

Core Logging Infrastructure

# Configure logging with TestAdapter for testing
adapter = %Statifier.Logging.TestAdapter{max_entries: 100}
state_chart = StateChart.configure_logging(state_chart, adapter, :debug)

# Configure logging with ElixirLoggerAdapter for production
adapter = %Statifier.Logging.ElixirLoggerAdapter{}
state_chart = StateChart.configure_logging(state_chart, adapter, :info)

# Log messages with automatic metadata extraction
state_chart = LogManager.info(state_chart, "Processing started", %{action_type: "initialization"})
state_chart = LogManager.error(state_chart, "Validation failed", %{field: "email"})

# Inspect captured logs in tests
logs = TestAdapter.get_logs(state_chart)
error_logs = TestAdapter.get_logs(state_chart, :error)
state_chart = TestAdapter.clear_logs(state_chart)

Production Logging Integration

# Initialize state chart with production logging
{:ok, state_chart} = Interpreter.initialize(document)
adapter = %Statifier.Logging.ElixirLoggerAdapter{}
state_chart = StateChart.configure_logging(state_chart, adapter, :info)

# All state chart operations now include automatic logging
{:ok, state_chart} = Interpreter.send_event(state_chart, event)
# Logs: [info] Processing event "start" current_state=["idle"] event="start"

Test Environment Usage

defmodule MyStateMachineTest do
  use ExUnit.Case

  test "validates error logging during processing" do
    adapter = %Statifier.Logging.TestAdapter{max_entries: 50}
    state_chart = StateChart.configure_logging(state_chart, adapter, :debug)
    
    # ... perform operations that should log ...
    
    # Verify specific log messages were captured
    logs = TestAdapter.get_logs(state_chart)
    assert [%{level: :error, message: "Validation failed"}] = logs
    
    # Check metadata extraction
    assert logs |> hd() |> Map.get(:metadata) |> Map.get(:current_state) == ["processing"]
  end
end

Logging Configuration System

# Use default configuration (ElixirLoggerAdapter, :info level)
{:ok, state_chart} = Interpreter.initialize(document)

# Configure with runtime options
{:ok, state_chart} = Interpreter.initialize(document, [
  log_adapter: {TestAdapter, [max_entries: 50]},
  log_level: :debug
])

# Configure with direct adapter struct
adapter = %TestAdapter{max_entries: 100}
{:ok, state_chart} = Interpreter.initialize(document, 
  log_adapter: adapter,
  log_level: :trace
)

# Configure via application environment (in config files or test_helper.exs)
Application.put_env(:statifier, :default_log_adapter, {TestAdapter, [max_entries: 200]})
Application.put_env(:statifier, :default_log_level, :warn)
{:ok, state_chart} = Interpreter.initialize(document)  # Uses application config

Configuration Precedence Examples

# Application configuration
Application.put_env(:statifier, :default_log_adapter, {TestAdapter, [max_entries: 300]})
Application.put_env(:statifier, :default_log_level, :error)

# Runtime options override application config
{:ok, state_chart} = Interpreter.initialize(document, [
  log_adapter: {ElixirLoggerAdapter, []},  # Overrides TestAdapter
  log_level: :info                         # Overrides :error
])

# Invalid configurations fall back gracefully
{:ok, state_chart} = Interpreter.initialize(document, [
  log_adapter: {NonExistentModule, []}     # Falls back to ElixirLoggerAdapter
])

Changed

Logger to LogManager Migration

  • Centralized Logging Architecture: Migrated all existing Logger.* calls throughout the codebase to use the new LogManager.* API

    • ActionExecutor: All debug logging now uses LogManager.debug with structured metadata (action_type, state_id, phase, etc.)
    • LogAction: Replaced Logger.info with LogManager.info, now returns updated StateChart from logging operations
    • RaiseAction: Migrated Logger.info to LogManager.info with event metadata, properly threads StateChart through logging calls
    • AssignAction: Updated Logger.error to LogManager.error with comprehensive error context and assignment details
    • Datamodel: Replaced Logger.debug calls with LogManager.debug for expression evaluation failures
  • Structured Logging Enhancement: All LogManager calls now include appropriate context-specific metadata

    • Action Context: Debug logs include action_type, state_id, and execution phase information
    • Error Context: Error logs include detailed failure information, locations, and expressions
    • Event Context: Event-related logs include event names and metadata
    • Expression Context: Expression evaluation logs include compiled expressions and error details
  • StateChart Threading: Actions now properly return updated StateChart instances from logging operations

    • Consistent Return Values: All action modules maintain StateChart consistency through logging calls
    • State Preservation: Logging operations preserve and return the complete StateChart state
    • Queue Management: Internal and external event queues remain intact through logging operations

Log Storage Optimization

  • Chronological Log Ordering: TestAdapter now stores logs in intuitive chronological order (oldest first, newest last)

    • Natural Reading Order: Logs now appear in the order they were created for easier debugging
    • Standard Behavior: Aligns with typical logging system expectations and developer intuitions
    • Improved Test Assertions: assert_log_order now uses ascending index order for cleaner test logic
  • Memory Management: Updated circular buffer behavior to maintain chronological ordering

    • FIFO Behavior: When max_entries limit is reached, oldest entries are removed first
    • Append Operations: New log entries are appended to maintain chronological sequence
    • Backward Compatibility: API remains unchanged while improving internal behavior

Test Infrastructure Enhancements

  • StateChart Log Integration: All action tests now use StateChart logs instead of capture_log for verification

    • Helper Functions: Added test_state_chart() helper for properly configured StateChart instances
    • Log Assertions: Created assert_log_entry() and assert_log_order() helpers for clean log verification
    • Configuration Helpers: Added create_configured_state_chart() helpers to reduce test duplication
  • Test Coverage Maintenance: Maintained 91.2% test coverage with comprehensive log assertion coverage

    • Regression Protection: All 108 regression tests continue passing with new logging infrastructure
    • Action Coverage: Complete test coverage for all action logging behaviors
    • Error Handling: Comprehensive test coverage for logging error scenarios

[1.2.0] 2025-08-27

Added

If/Else/ElseIf Conditional Action Support

  • <if> Action Support: Full implementation of SCXML <if> elements with conditional execution

    • Statifier.Actions.IfAction Struct: Represents if/elseif/else conditional blocks
    • Nested Action Execution: Supports multiple actions within each conditional block
    • Expression Evaluation: Uses Statifier.Evaluator for condition evaluation
    • ActionExecutor Integration: Seamlessly integrates with existing action execution framework
    • Complex Conditionals: Support for if/elseif/else chains with proper precedence
  • Parser Extensions for Conditional Actions: Extended SCXML parser to handle conditional elements

    • If/ElseIf/Else Parsing: Complete parsing support for conditional action blocks
    • StateStack Integration: Proper conditional block handling in parsing state stack
    • Mixed Action Support: Parse conditional actions alongside log/raise/assign actions
    • Location Tracking: Complete source location tracking for debugging conditional blocks

Test Coverage Improvements

  • 90.8% Overall Coverage: Comprehensive test coverage improvements through targeted edge case testing

    • StateStack Coverage: Improved from 72.7% to 95.8% (+23.1% - biggest impact module)
    • ActionExecutor Edge Cases: Added comprehensive error handling and edge case tests
    • Interpreter Coverage: Added simple edge case tests avoiding duplication with existing functionality
    • Handler Coverage: Added unknown element handling and parsing edge case tests
    • 4 New Test Files: Comprehensive coverage tests for critical modules
  • Enhanced LogAction: Improved string evaluation and error handling

    • Evaluator Integration: Uses Statifier.Evaluator for consistent expression handling
    • String Validation: Proper Unicode string validation and safe logging
    • Fallback Parsing: Graceful fallback for quoted string parsing
    • Error Recovery: Continues execution even with invalid expressions

Architecture Improvements

  • Unified Statifier.Evaluator Module: Consolidated ConditionEvaluator and ValueEvaluator into single module

    • Single Entry Point: One module for all expression evaluation (conditions and values)
    • Improved Maintainability: Eliminated code duplication between evaluator modules
    • Future Extensibility: Better prepared for pluggable datamodel architectures (ECMAScript, XPath)
    • Consistent API: Unified function signatures and error handling patterns
  • Enhanced Statifier.Datamodel Module: Improved data model operations and separation of concerns

    • put_in_path/3 Function: Moved from Evaluator to Datamodel for better architecture
    • Improved Error Handling: Returns {:ok, result} | {:error, reason} instead of raising exceptions

    • Type Safety: Proper Datamodel.t() typing throughout the codebase
    • Data Model Operations: Centralized location for all data model manipulation logic
  • Feature Detection Updates: Enhanced SCXML feature tracking for better test validation

    • Datamodel Support: Marked :datamodel and :data_elements as :supported in feature registry
    • Accurate Test Results: Prevents false test failures from unsupported feature detection
    • Better Compliance Tracking: Improved visibility into SCXML feature implementation status

Changed

Action Execution Architecture

  • ActionExecutor Delegation Pattern: Improved action execution through proper delegation

    • Public execute_single_action/2: Made function public for IfAction integration
    • Action Delegation: ActionExecutor now properly delegates to action.execute/2 methods
    • Centralized Execution: All actions now execute through consistent ActionExecutor interface
    • Better Separation of Concerns: Each action type handles its own execution logic
  • Code Quality Improvements: Enhanced code maintainability and compliance

    • Zero Credo Issues: All static analysis issues resolved across the codebase
    • Unused Variable Cleanup: Fixed unused variable warnings in Handler and ElementBuilder
    • Alias Ordering: Proper alphabetical alias ordering in all test files
    • Clean Validation Pipeline: All steps pass - format ✓ test ✓ credo ✓ dialyzer ✓

Test Coverage Improvements

  • 13 New Passing Tests: Unlocked additional test coverage through datamodel improvements

    • 9 SCION Tests: Including assign actions, current small step assignments, data initialization
    • 4 W3C Tests: Including executable content evaluation, foreach loops, conditional execution
    • Test Categories: assign/, assign_current_small_step/, data/, foreach/, if_else/
    • Overall Progress: Improved from 48/184 to 61/184 total tests passing (33% compliance)
  • Updated Test Baseline: Added new passing tests to regression test suite

    • SCION Tests: 44 → 53 passing tests
    • W3C Tests: 5 → 9 passing tests
    • Maintained Quality: All 98 regression tests continue to pass

Examples

If/Else/ElseIf Conditional Actions

<?xml version="1.0" encoding="UTF-8"?>
<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" initial="start">
  <state id="start">
    <onentry>
      <assign location="score" expr="85"/>
      <if cond="score >= 90">
        <assign location="grade" expr="'A'"/>
        <log label="grade" expr="'Excellent work!'"/>
      <elseif cond="score >= 80"/>
        <assign location="grade" expr="'B'"/>
        <log label="grade" expr="'Good job!'"/>
      <elseif cond="score >= 70"/>
        <assign location="grade" expr="'C'"/>
        <log label="grade" expr="'Satisfactory'"/>
      <else/>
        <assign location="grade" expr="'F'"/>
        <log label="grade" expr="'Needs improvement'"/>
      </if>
    </onentry>
  </state>
</scxml>

Nested Conditional Logic

<state id="processing">
  <onentry>
    <if cond="user.authenticated">
      <if cond="user.role == 'admin'">
        <assign location="permissions" expr="'full'"/>
        <raise event="admin_access"/>
      <else/>
        <assign location="permissions" expr="'limited'"/>
        <raise event="user_access"/>
      </if>
    <else/>
      <assign location="permissions" expr="'none'"/>
      <raise event="auth_required"/>
    </if>
  </onentry>
</state>

Mixed Actions with Conditionals

<state id="validation">
  <onentry>
    <log label="status" expr="'Starting validation'"/>
    <assign location="errors" expr="[]"/>
    <if cond="data.email == null">
      <assign location="errors[0]" expr="'Email required'"/>
    </if>
    <if cond="data.age < 18">
      <assign location="errors[1]" expr="'Must be 18 or older'"/>
    </if>
    <if cond="errors.length > 0">
      <raise event="validation_failed"/>
    <else/>
      <raise event="validation_passed"/>
    </if>
  </onentry>
</state>

[1.1.0] 2025-08-26

Added

Phase 1 Enhanced Expression Evaluation

  • Predicator v3.0 Integration: Upgraded from v2.0 to v3.0 with enhanced capabilities

    • Enhanced Nested Property Access: Deep dot notation support (user.profile.settings.theme)
    • Mixed Access Patterns: Combined bracket/dot notation (users['john'].active)
    • Context Location Resolution: New context_location/2 function for assignment path validation
    • Value Evaluation: Non-boolean expression evaluation for actual data values
    • Type-Safe Operations: Improved type coercion and error handling
    • Graceful Fallback: Returns :undefined for missing properties instead of errors
  • Statifier.ValueEvaluator Module: Comprehensive value evaluation system for SCXML expressions

    • Expression Compilation: compile_expression/1 for reusable expression compilation
    • Value Evaluation: evaluate_value/2 extracts actual values (not just boolean results)
    • Location Path Resolution: resolve_location/1,2 validates assignment paths using predicator v3.0
    • Safe Assignment: assign_value/3 performs type-safe nested data model updates
    • Integrated Assignment: evaluate_and_assign/3 combines evaluation and assignment
    • SCXML Context Support: Full integration with state machine context (events, configuration, datamodel)
    • Error Handling: Comprehensive error handling with detailed logging
  • <assign> Element Support: Full W3C SCXML assign element implementation

    • Statifier.Actions.AssignAction Struct: Represents assign actions with location and expr attributes
    • Location-Based Assignment: Validates assignment paths before execution
    • Expression Evaluation: Uses Statifier.ValueEvaluator for complex expression processing
    • Nested Property Assignment: Supports deep assignment (user.profile.name = "John")
    • Mixed Notation Support: Handles both dot and bracket notation in assignments
    • Context Integration: Access to current event data and state configuration
    • Error Recovery: Graceful error handling with logging, continues execution on failures
    • Action Integration: Seamlessly integrates with existing action execution framework

StateChart Data Model Enhancement

  • Datamodel Storage: Added datamodel field to Statifier.StateChart for variable persistence
  • Current Event Context: Added current_event field for expression evaluation context
  • Helper Methods: update_datamodel/2 and set_current_event/2 for state management
  • SCXML Context Building: Enhanced context building for comprehensive expression evaluation

Parser Extensions

  • Assign Element Parsing: Extended SCXML parser to handle <assign> elements
    • Element Builder: build_assign_action/4 creates AssignAction structs with location tracking
    • Handler Integration: Added assign element start/end handlers
    • StateStack Integration: handle_assign_end/1 properly collects assign actions
    • Mixed Action Support: Parse assign actions alongside log/raise actions in onentry/onexit
    • Location Tracking: Complete source location tracking for debugging

Feature Detection Updates

  • Assign Elements Support: Updated assign_elements feature status to :supported
  • Feature Registry: Enhanced feature detection for new capabilities
  • Test Infrastructure: Tests now recognize assign element capability

Changed

Dependency Updates

  • predicator: Upgraded from ~> 2.0 to ~> 3.0 (major version upgrade)
    • Breaking Change: Enhanced property access semantics
    • Migration: Context keys with dots now require nested structure (e.g., %{"user" => %{"email" => "..."}} instead of %{"user.email" => "..."})
    • Benefit: More powerful and flexible data access patterns

Technical Improvements

  • Test Coverage: Maintained 92.9% overall code coverage with comprehensive new tests
    • New Test Modules: Statifier.ValueEvaluatorTest, Statifier.Actions.AssignActionTest, Statifier.Parser.AssignParsingTest
    • 556 Total Tests: All tests pass including new assign functionality
    • Log Capture: Added @moduletag capture_log: true for clean test output
  • Performance: O(1) lookups maintained with new data model operations
  • Error Handling: Enhanced error handling and logging throughout assign operations
  • Code Quality: Maintained Credo compliance with proper alias ordering

Examples

Basic Assign Usage

<?xml version="1.0" encoding="UTF-8"?>
<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" initial="start">
  <state id="start">
    <onentry>
      <assign location="userName" expr="'John Doe'"/>
      <assign location="counter" expr="42"/>
      <assign location="user.profile.name" expr="'Jane Smith'"/>
    </onentry>
    <transition target="working"/>
  </state>
  <state id="working">
    <onentry>
      <assign location="counter" expr="counter + 1"/>
      <assign location="status" expr="'processing'"/>
    </onentry>
  </state>
</scxml>

Mixed Notation Assignment

<onentry>
  <assign location="users['admin'].active" expr="true"/>
  <assign location="settings.theme" expr="'dark'"/>
  <assign location="counters[0]" expr="counters[0] + 1"/>
</onentry>

Event Data Assignment

<state id="processing">
  <onentry>
    <assign location="lastEvent" expr="_event.name"/>
    <assign location="eventData" expr="_event.data.value"/>
  </onentry>
</state>

Programmatic Usage

# Value evaluation
{:ok, compiled} = Statifier.ValueEvaluator.compile_expression("user.profile.name")
{:ok, "John Doe"} = Statifier.ValueEvaluator.evaluate_value(compiled, context)

# Location validation
{:ok, ["user", "settings", "theme"]} = Statifier.ValueEvaluator.resolve_location("user.settings.theme")

# Combined evaluation and assignment
{:ok, updated_model} = Statifier.ValueEvaluator.evaluate_and_assign("result", "count * 2", context)

Notes

  • Phase 1 Complete: Enhanced Expression Evaluation phase is fully implemented
  • Foundation for Phase 2: Data model and expression evaluation infrastructure ready
  • Backward Compatible: All existing functionality preserved
  • Production Ready: Comprehensive test coverage and error handling
  • SCION Progress: assign_elements feature now supported (awaiting Phase 2 for full datamodel tests)

[1.0.0] - 2025-08-23

Added

Phase 1 Executable Content Support

  • <log> Action Support: Full implementation of SCXML <log> elements with expression evaluation
    • Statifier.LogAction Struct: Represents log actions with label and expr attributes
    • Expression Evaluation: Basic literal expression support (full evaluation in Phase 2)
    • Logger Integration: Uses Elixir Logger for output with contextual information
    • Location Tracking: Complete source location tracking for debugging
  • <raise> Action Support: Complete implementation of SCXML <raise> elements for internal event generation
    • Statifier.RaiseAction Struct: Represents raise actions with event attribute
    • Event Generation: Logs raised events (full event queue integration in future phases)
    • Anonymous Events: Handles raise elements without event attributes
  • <onentry> and <onexit> Action Support: Executable content containers for state transitions
    • Action Collection: Parses and stores multiple actions within onentry/onexit blocks
    • Mixed Actions: Support for combining log, raise, and future action types
    • State Integration: Actions stored in Statifier.State struct with onentry_actions/onexit_actions fields
  • Action Execution Infrastructure: Comprehensive system for executing SCXML actions
    • Statifier.ActionExecutor Module: Centralized action execution with phase tracking
    • Interpreter Integration: Actions executed during state entry/exit in interpreter lifecycle
    • Type Safety: Pattern matching for different action types with extensibility

Test Infrastructure Improvements

  • Required Features System: Automated test tagging system for feature-based test exclusion
    • @tag required_features: annotations on all W3C and SCION tests
    • Feature Detection Integration: Tests automatically excluded if required features unsupported
    • 262 Tests Tagged: Comprehensive coverage of W3C and SCION test requirements
    • Maintainable System: Script-based tag updates for easy maintenance

Eventless/Automatic Transitions

  • Eventless Transitions: Full W3C SCXML support for transitions without event attributes that fire automatically
  • Automatic Transition Processing: Microstep loop processes chains of eventless transitions until stable configuration
  • Cycle Detection: Prevents infinite loops with configurable iteration limits (100 iterations default)
  • Parallel Region Preservation: Proper SCXML semantics for transitions within and across parallel regions
  • Conflict Resolution: Child state transitions take priority over ancestor transitions per W3C specification

Enhanced Parallel State Support

  • Parallel State Transitions: Fixed regression where transitions within parallel regions affected unrelated parallel regions
  • Cross-Parallel Boundaries: Proper exit semantics when transitions cross parallel region boundaries
  • SCXML Exit State Calculation: Implements correct W3C exit set computation for complex state hierarchies
  • Sibling State Management: Automatic exit of parallel siblings when transitions leave their shared parent

Fixed

  • Regression Test: Fixed parallel state test failure (test/scion_tests/more_parallel/test1_test.exs)
  • SCION Test Suite: All 4 cond_js tests now pass (previously 3/4)
  • Parallel Interrupt Tests: Fixed 6 parallel interrupt test failures in regression suite
  • Code Quality: Resolved all mix credo --strict issues (predicate naming, unused variables, aliases)
  • Pattern Matching Refactoring: Converted Handler module case statements to idiomatic Elixir pattern matching
    • handle_event(:end_element, ...) Function: Refactored to separate function clauses with pattern matching
    • dispatch_element_start(...) Function: Converted from case statement to pattern matching function clauses
    • StateStack Module: Applied same pattern matching refactoring to action handling functions

Changed (Breaking)

ActionExecutor API Modernization

  • REMOVED: Statifier.Actions.ActionExecutor.execute_onentry_actions/2 function clause that accepted %Document{} as second parameter
  • REMOVED: Statifier.Actions.ActionExecutor.execute_onexit_actions/2 function clause that accepted %Document{} as second parameter
  • BREAKING: These functions now only accept %StateChart{} as the second parameter for proper event queue integration
  • Migration: Replace ActionExecutor.execute_*_actions(states, document) with ActionExecutor.execute_*_actions(states, state_chart)
  • Benefit: Action execution now properly integrates with the StateChart event queue system, enabling raised events to be processed correctly

Technical Improvements

  • SCXML Terminology Alignment: Updated codebase to use proper SCXML specification terminology
    • Microstep/Macrostep Processing: Execute microsteps (single transition sets) until stable macrostep completion
    • Exit Set Computation: Implements W3C SCXML exit set calculation algorithm for proper state exit semantics
    • LCCA Computation: Full Least Common Compound Ancestor algorithm for accurate transition conflict resolution
    • NULL Transitions: Added SCXML specification references while maintaining "eventless transitions" terminology
  • Feature Detection: Enhanced feature registry with newly supported capabilities
    • Added eventless_transitions: :supported to feature registry
    • Added log_elements: :supported for log action support
    • Added raise_elements: :supported for raise action support
    • Maintained onentry_actions: :supported and onexit_actions: :supported** status
  • Performance: Optimized ancestor/descendant lookup using existing parent attributes
  • Test Coverage: Comprehensive testing across all new functionality
    • Total Tests: 461 tests (up from 444), including extensive executable content testing
    • New Test Files: 13 comprehensive test files for log/raise actions and execution
    • Coverage Improvement: Interpreter module coverage increased from 70.4% to 83.0%
    • Project Coverage: Overall coverage improved from 89.0% to 92.3% (exceeds 90% minimum requirement)
  • Regression Testing: All core functionality tests pass with no regressions

[0.1.0] - 2025-08-20

Added

Core SCXML Implementation

  • W3C SCXML Parser: Full XML parser supporting SCXML 1.0 specification
  • State Machine Interpreter: Synchronous, functional API for state chart execution
  • State Configuration Management: Efficient tracking of active states with O(1) lookups
  • Event Processing: Support for internal and external events with proper queueing
  • Document Validation: Comprehensive validation with detailed error reporting

SCXML Elements Support

  • <scxml>: Root element with version, initial state, and namespace support
  • <state>: Compound and atomic states with nested hierarchy
  • <initial>: Initial state pseudo-states for deterministic startup
  • <transition>: Event-driven transitions with conditions and targets
  • <data>: Data model elements for state machine variables

Conditional Expressions

  • cond Attribute: Full support for conditional expressions on transitions
  • Predicator Integration: Secure expression evaluation using predicator library v2.0.0
  • SCXML In() Function: W3C-compliant state checking predicate
  • Logical Operations: Support for AND, OR, NOT, and comparison operators
  • Event Data Access: Conditions can access current event name and payload
  • Error Handling: Invalid expressions gracefully handled per W3C specification
  • Modern Functions API: Uses Predicator v2.0's improved custom functions approach

Performance Optimizations

  • Parse-time Compilation: Conditional expressions compiled once during parsing
  • O(1) State Lookups: Fast state and transition resolution using hash maps
  • Document Order Processing: Deterministic transition selection
  • Memory Efficient: Minimal memory footprint with optimized data structures

Developer Experience

  • Comprehensive Testing: 426+ test cases covering all functionality
  • Integration Tests: End-to-end testing with real SCXML documents
  • Type Safety: Full Elixir typespec coverage for all public APIs
  • Documentation: Detailed module and function documentation
  • Error Messages: Clear, actionable error reporting with location information

Validation & Quality

  • State ID Validation: Ensures unique and valid state identifiers
  • Transition Validation: Validates target states exist and are reachable
  • Initial State Validation: Enforces SCXML initial state constraints
  • Reachability Analysis: Identifies unreachable states in state charts
  • Static Analysis: Credo-compliant code with strict quality checks

Test Coverage

  • W3C Compliance: Support for W3C SCXML test cases (excluded by default)
  • SCION Compatibility: Integration with SCION test suite for validation
  • Unit Tests: Comprehensive unit testing of all modules
  • Integration Tests: Real-world SCXML document processing
  • Regression Tests: Critical functionality protection

Dependencies

  • saxy ~> 1.6: Fast XML parser for SCXML document processing
  • predicator ~> 2.0: Secure conditional expression evaluation (upgraded to v2.0 with improved custom functions API)
  • credo ~> 1.7: Static code analysis (dev/test)
  • dialyxir ~> 1.4: Static type checking (dev/test)
  • excoveralls ~> 0.18: Test coverage analysis (test)

Technical Specifications

  • Elixir: Requires Elixir ~> 1.17
  • OTP: Compatible with OTP 26+
  • Architecture: Functional, immutable state machine implementation
  • Concurrency: Thread-safe, stateless evaluation
  • Memory: Efficient MapSet-based state tracking

Examples

Basic State Machine

<?xml version="1.0" encoding="UTF-8"?>
<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" initial="idle">
  <state id="idle">
    <transition event="start" target="working"/>
  </state>
  <state id="working">
    <transition event="finish" target="done"/>
  </state>
  <state id="done"/>
</scxml>

Conditional Transitions

<state id="validation">
  <transition event="submit" cond="score > 80" target="approved"/>
  <transition event="submit" cond="score >= 60" target="review"/>
  <transition event="submit" target="rejected"/>
</state>

SCXML In() Function

<state id="processing">
  <transition event="check" cond="In('processing') AND progress > 50" target="almost_done"/>
  <transition event="check" target="continue_working"/>
</state>

Usage

# Parse SCXML document
{:ok, document} = Statifier.Parser.SCXML.parse(scxml_string)

# Initialize state machine
{:ok, state_chart} = Statifier.Interpreter.initialize(document)

# Send events
event = %Statifier.Event{name: "start", data: %{}}
{:ok, new_state_chart} = Statifier.Interpreter.send_event(state_chart, event)

# Check active states
active_states = new_state_chart.configuration.active_states

Notes

  • This is the initial release of the Statifier SCXML library
  • Full W3C SCXML 1.0 specification compliance for supported features
  • Production-ready with comprehensive test coverage
  • Built for high-performance state machine processing in Elixir applications
  • Uses Predicator v2.0 with modern custom functions API (no global function registry)