Architecture
src/ kernel/ reactive.ts Signal, Computed, effect, batch, untracked, onSettled (no DOM) state.ts createStore(): path-keyed signals, merge-patch, snapshot, proxies (no DOM) compile.ts transform(): $name/@action rewrite; compile(): strict-mode functions of ($, __a, el, evt, …) (no DOM) strict-csp.ts cspCompiler(nonce) runtime.ts createSigmx(): scanning, mounting, MutationObserver, error isolation contracts.ts Ctx, Runtime, plugin and option types lib/ objects (isPlain, expand, filters), casing, schedule (debounce, throttle, mods), sse plugins/ directives/ one file per attribute functions/ @-functions, including the request client server-events/ patch-signals, patch-elements, morph presets/ minimal, essentials, all standalone.ts script-tag entryReactivity
Section titled “Reactivity”A Signal holds a value and a version. Reading inside a tracker records (signal, version); writing bumps the version and invalidates subscribers. A Computed is a signal with a getter: invalidation marks it dirty and propagates; reading refreshes it, and its version moves only when the result changes. Effects are queued on invalidation; when the queue flushes, an effect first checks whether any recorded dependency version moved (refreshing computeds on the way) and skips the run if not. Effects created inside an effect are disposed when the parent re-runs. batch defers flushing, and onSettled runs after a flush, which is how the store emits one patch event per transaction.
A Map<path, Signal> for leaves and a Map<path, Set<name>> for namespaces. One shape signal is bumped on any structural change; enumerations and unknown-path reads subscribe to it, so a reader of a not-yet-existing signal re-runs when the signal appears. Plain objects always become namespaces; arrays and other objects are leaves, and arrays are handed out through a proxy that notifies on mutation. The namespace proxy ($) sits on top for property access and enumeration; expressions reach signals through it, since $name compiles to $.name.
Expressions
Section titled “Expressions”transform(src) is a small tokenizer: it copies strings, template text, comments and regular expressions untouched, recurses into ${…} holes, and rewrites $name to $.name and @name( to __a.name(. compile(compiler, src, params) then tries, in order, return (code), each split at a ; from the end with the last part returned, and plain statements, all as strict-mode functions of ($, __a, el, evt, …args) where $ is the root store proxy. The build-time precompiler emits exactly these bodies, so there is one set of semantics. Results are cached by source and parameter list.
Engine
Section titled “Engine”createSigmx builds a Runtime (prefixes, store, compiler, plugin maps, emit, handle) and scans roots: for every attribute whose name starts with a configured prefix it parses plugin[:key][__mod.arg]*, validates the plugin’s key and value requirements, builds a Ctx, and calls mount. Everything the plugin registers through the context is pushed onto a disposer list keyed by element and attribute. A MutationObserver mounts added subtrees, unmounts removed ones and remounts changed attributes. Errors from mount, effects and listeners go to onError.
Before walking, the morph computes the set of ids present in both trees with matching tag names and keeps a map from id to the existing element. Walking children, a kept id that lies ahead among the old siblings has whatever precedes it dropped; one that lives elsewhere (moved, or inside a subtree being removed) is pulled into place with moveBefore where available, so it never leaves the document and keeps focus, media and iframe state. Other nodes match positionally by node type and tag. A removed subtree that still contains kept ids has those elements left connected until they are moved, and a new element that wants kept descendants is created as a shell (same tag and attributes, no children) for them to be pulled into. Form state is compared by attribute, not live value.