Fluid Signals background


This page contains background information motivating the design of fluid.cell, the reactive primitive built to support Infusion 6, explaining how it differs from traditional reactive libraries. There are also API docs for fluid.cell and also a more extended essay explaining its goals in the context of a malleable substrate.

I chose the name “cell”, rather than “signal” which is more common in the 2020s JavaScript reactive community, in honour of 3 traditions:

  • Spreadsheet cells, which are the public’s primeval grounding in reactivity.
  • Kenny Tilton’s Cells system for CLOS, which in 2001, 5 years before the coining of the terms “glitch” and “reactive programming”, had already solved all the problems of reactivity embedded in imperative programming languages.
  • The notion of a reference cell in JavaScript and and other functional languages, taking the form of an object + key or 1-element array which mocks up the mutable capability of a pointer in languages which lack them.

There is now a consensus notion of the capabilities of a reactive library in JavaScript, amongst such popular implementations as preact-signals, alien-signals, Angular signals, Solid signal, etc. which has led to the proposal of a standardised JS implementation by the TC39 committee. This consensus baseline is often called “commodity signals” in pages on this site.

Whilst the core implementation of fluid.cell is consistent with this consensus implementation — in fact, it has been adopted directly from Milo Mighdoll’s amazing and minimal reactively system — this implementation departs from the consensus in some significant ways. These departures mostly widen the domain of validity of the implementation, relaxing restrictions which are incompatible with the demands of a substrate supporting malleable programming.

Whilst its idiom and goals are aligned with support for the Infusion 6 substrate, fluid.cell is a self-contained library with no dependencies which could be adopted for other purposes.

You can read its test cases here and run them here.

The best guide to reactive programming in general and fluid.cell’s algorithm in particular is Milo’s 2022 Super Charging Fine-Grained Reactivity which contrasts a variety of implementations including Reactively’s, about 2/3 of the way down the page with the red and green squares, which I have adopted.

Extra capabilities in fluid.cell

Bidirectional relationships

The most significant ergonomic difference in fluid.cell is support for bidirectional relationships between cells, which supports democratic constructs such as lenses without recourse to gratuitous asymmetries such as writable computed relations.

Multiple inbound compute arcs

Support for bidirectional relationships entails support for multiple computed relations capable of updating the value of a single cell. This is a capability not found in any popular reactive library. In isolation, this capability might lead to the possibility for inconsistent or conflicting updates — however, fluid.cell follows the insight from Dan Ingall’s Fabrik system which recognizes “bidirectional dataflow connections as a shorthand for multiple paths of flow”. As one leg of a bidirectional relationship is activated, the reverse leg is removed from consideration for this reactive update cycle (this kind of cycle is named a “fit”), removing the chance for cyclic updates.

Static compute arguments

Tied in with support for lenses and multiple incoming arcs is fluid.cell’s support for static arguments to compute arcs. This is a significant departure from commodity signals, which pride themselves on automatic discovery of dependencies as code executes in the compute arc. In a substrate forming an integration domain this is unidiomatic since base language code should ideally have no power of reference to surrounding scopes, in accordance with the principle of interface hiding. However, the use of static arguments isn’t mandatory, standard dynamic tracked dependencies are available to be used either instead of or along with them, to aid experts in interoperating with established codebases.

Cycle diagnostics

Another difference is the treatment of cycles. Rather than being rejected outright with a base language exception, these can be configured to either resolve benignly through having the cycle broken by deferring a potentially cyclic update to a further task, or else to resolve to an unavailable value which accumulates the addresses of the nodes involved, producing a visible diagnostic in the substrate[coming soon].

Cause tracking

A hugely important ergonomic improvement is being able to report the cause of any ongoing reaction, that is, the chain of updated nodes leading back to the initial changed node triggered by the user or environment. This is vital in a substrate which positions reactivity as a wholesale replacement for execution, and therefore needs to replace the invaluable explanatory power of the stack with a correspondingly thorough explanation stretching across the substrate. This is done with the fluid.findCause API which returns this path of updated nodes.

Asynchronous compute arcs

Finally, fluid.cell supports compute arcs which produce their results asynchronously. Whilst this is uncommon in reactive libraries, there is precedent for this in Ryan Carniato’s Solid signals supporting the upcoming 2.0 release of Solid, and Mike Bostock’s Runtime which powers Observable. fluid.cell passes the test cases (as of Nov 2025) issued for Solid signals’ createAsync.