<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Lorenzo Muscherà</title>
    <description>The latest articles on DEV Community by Lorenzo Muscherà (@lorenzomuschera).</description>
    <link>https://dev.to/lorenzomuschera</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4038165%2Fcfeb62e0-de56-4db3-a9fa-4092e1e7c320.png</url>
      <title>DEV Community: Lorenzo Muscherà</title>
      <link>https://dev.to/lorenzomuschera</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/lorenzomuschera"/>
    <language>en</language>
    <item>
      <title>Your Form Is Not Portable If It Contains Callbacks</title>
      <dc:creator>Lorenzo Muscherà</dc:creator>
      <pubDate>Mon, 24 Aug 2026 15:30:11 +0000</pubDate>
      <link>https://dev.to/lorenzomuschera/your-form-is-not-portable-if-it-contains-callbacks-52d9</link>
      <guid>https://dev.to/lorenzomuschera/your-form-is-not-portable-if-it-contains-callbacks-52d9</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;What makes a form portable? Not JSON alone. Its validation, conditions, collections and submission semantics must survive the trip too. I wrote about the architecture behind Modyra and the trade-offs involved.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Your Form Is Not Portable If It Contains Callbacks
&lt;/h2&gt;

&lt;p&gt;Most form libraries help us manage forms inside an application.&lt;/p&gt;

&lt;p&gt;They track values, execute validators, expose errors and eventually produce a submission payload.&lt;/p&gt;

&lt;p&gt;That works well until the form needs to exist somewhere else.&lt;/p&gt;

&lt;p&gt;Perhaps its structure comes from a backend. Perhaps a visual builder generates it. Perhaps multiple applications must render it. Perhaps the server must independently validate the same conditional rules used by the browser.&lt;/p&gt;

&lt;p&gt;At that point, the form is no longer just component state.&lt;/p&gt;

&lt;p&gt;It is a contract.&lt;/p&gt;

&lt;p&gt;And most form abstractions cannot cross that boundary.&lt;/p&gt;

&lt;h2&gt;
  
  
  The portability illusion
&lt;/h2&gt;

&lt;p&gt;Consider a typical conditional validator:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;form&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createForm&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;defaultValues&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;country&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;IT&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;vatId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="na"&gt;validators&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;country&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;IT&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vatId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;vatId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;VAT ID is required in Italy&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is perfectly reasonable application code.&lt;/p&gt;

&lt;p&gt;It is also not portable.&lt;/p&gt;

&lt;p&gt;The callback cannot travel through an API as JSON. A Java service cannot execute it. A visual editor cannot reliably inspect it. Another runtime cannot reproduce its meaning without receiving executable source code.&lt;/p&gt;

&lt;p&gt;We can serialize the values around the callback, but not the behavior itself.&lt;/p&gt;

&lt;p&gt;This leads to an important distinction:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A form configuration is not a portable form contract if part of its meaning still lives inside executable callbacks.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The obvious shortcuts are dangerous
&lt;/h2&gt;

&lt;p&gt;There are several tempting ways to work around this limitation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Serialize the callback as source code
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"condition"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"value.country === 'IT'"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The receiving application must now parse or execute an expression encoded as text.&lt;/p&gt;

&lt;p&gt;That creates immediate problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the expression is not statically connected to the form model;&lt;/li&gt;
&lt;li&gt;renaming a field may not update the expression;&lt;/li&gt;
&lt;li&gt;invalid paths are discovered at runtime;&lt;/li&gt;
&lt;li&gt;every runtime needs an equivalent interpreter;&lt;/li&gt;
&lt;li&gt;executing arbitrary source introduces a dangerous trust boundary.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Invent a compact string DSL
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"requiredWhen"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"country=IT"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This looks concise until the language grows.&lt;/p&gt;

&lt;p&gt;Soon it needs nested fields, arrays, grouping, precedence, comparisons, arithmetic, null handling and useful diagnostics.&lt;/p&gt;

&lt;p&gt;The compact syntax gradually becomes a programming language hidden inside strings.&lt;/p&gt;

&lt;p&gt;TypeScript cannot help much because TypeScript only sees a string.&lt;/p&gt;

&lt;h3&gt;
  
  
  Keep the behavior inside every client
&lt;/h3&gt;

&lt;p&gt;Another option is to let the backend send the field structure while every application implements the business rules independently.&lt;/p&gt;

&lt;p&gt;This avoids remote code execution, but destroys the main benefit of a shared contract.&lt;/p&gt;

&lt;p&gt;Different clients can interpret the same form differently.&lt;/p&gt;

&lt;p&gt;The contract becomes a suggestion.&lt;/p&gt;

&lt;h2&gt;
  
  
  Behavior can be data
&lt;/h2&gt;

&lt;p&gt;The alternative is to represent behavior through a closed, declarative expression tree.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"op"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eq"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"left"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"country"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"right"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"literal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"IT"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A conditional effect can also remain ordinary data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"when"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"op"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eq"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"left"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"country"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"right"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"literal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"IT"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"then"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"required"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"field"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"vatId"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This representation is more verbose than a callback.&lt;/p&gt;

&lt;p&gt;It is also:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;serializable;&lt;/li&gt;
&lt;li&gt;inspectable;&lt;/li&gt;
&lt;li&gt;versionable;&lt;/li&gt;
&lt;li&gt;independently validatable;&lt;/li&gt;
&lt;li&gt;executable by different runtimes;&lt;/li&gt;
&lt;li&gt;safe to reject when unsupported.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The remote document never contains JavaScript, serialized callbacks or instructions to evaluate arbitrary source code.&lt;/p&gt;

&lt;p&gt;It only contains data from a closed vocabulary.&lt;/p&gt;

&lt;h2&gt;
  
  
  TypeScript for authoring, JSON for transport
&lt;/h2&gt;

&lt;p&gt;Writing raw expression trees directly is not a pleasant developer experience.&lt;/p&gt;

&lt;p&gt;The authoring format should therefore be different from the transport format.&lt;/p&gt;

&lt;p&gt;A typed builder can expose references checked by TypeScript:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;supplier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;group&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;legalName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;label&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Legal name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;required&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;

    &lt;span class="na"&gt;country&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;countries&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;IT&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;

    &lt;span class="na"&gt;riskLevel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;low&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;medium&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;high&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;low&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;

  &lt;span class="na"&gt;dueDiligence&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;upload&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;label&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Due diligence document&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;procurementForm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;form&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;procurement-request&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="na"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nf"&gt;when&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;supplier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;riskLevel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;high&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dueDiligence&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The author writes TypeScript, receives autocomplete and cannot casually reference a field that does not exist.&lt;/p&gt;

&lt;p&gt;The builder produces the canonical JSON representation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;contract&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;procurementForm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toJSON&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The crucial requirement is that builders must produce data immediately.&lt;/p&gt;

&lt;p&gt;They must not inspect JavaScript source, serialize callbacks or defer arbitrary functions into the resulting contract.&lt;/p&gt;

&lt;p&gt;The pipeline should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Typed authoring
      ↓
Canonical contract
      ↓
JSON transport
      ↓
Runtime validation
      ↓
Form engine
      ↓
Renderer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This separation became one of the central ideas behind &lt;strong&gt;Modyra&lt;/strong&gt;, the open-source form engine I have been building for TypeScript applications.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rendering is only one consumer
&lt;/h2&gt;

&lt;p&gt;Once the form becomes a contract, a frontend renderer is no longer the owner of the form.&lt;/p&gt;

&lt;p&gt;It is one possible consumer.&lt;/p&gt;

&lt;p&gt;An Angular application can receive a contract and render it without knowing its fields in advance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;mdy-dynamic-form&lt;/span&gt;
  &lt;span class="na"&gt;[contract]=&lt;/span&gt;&lt;span class="s"&gt;"contract()"&lt;/span&gt;
&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A framework-free application can interpret the same contract using real DOM elements.&lt;/p&gt;

&lt;p&gt;A React application can consume the same form model through a headless adapter.&lt;/p&gt;

&lt;p&gt;A backend can validate submitted values against the same structural expectations.&lt;/p&gt;

&lt;p&gt;A visual editor can modify the document without generating callback source.&lt;/p&gt;

&lt;p&gt;The objective is not identical DOM across every framework.&lt;/p&gt;

&lt;p&gt;The objective is identical meaning.&lt;/p&gt;

&lt;h2&gt;
  
  
  Identical meaning is the difficult part
&lt;/h2&gt;

&lt;p&gt;Rendering a text input is easy.&lt;/p&gt;

&lt;p&gt;Preserving its semantics across boundaries is not.&lt;/p&gt;

&lt;p&gt;Consider a positional collection:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"sku"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"A"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"quantity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"sku"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"B"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"quantity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"sku"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"C"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"quantity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the second row becomes disabled and disabled values must be excluded from submission, should the payload become this?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"sku"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"A"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"quantity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"sku"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"C"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"quantity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That silently changes the identity of the third row.&lt;/p&gt;

&lt;p&gt;The item at index &lt;code&gt;2&lt;/code&gt; has moved to index &lt;code&gt;1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A safer representation may need to preserve the original position:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"sku"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"A"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"quantity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"sku"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"C"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"quantity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That decision influences:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;submission;&lt;/li&gt;
&lt;li&gt;change sets;&lt;/li&gt;
&lt;li&gt;patches;&lt;/li&gt;
&lt;li&gt;undo and redo;&lt;/li&gt;
&lt;li&gt;draft persistence;&lt;/li&gt;
&lt;li&gt;server validation;&lt;/li&gt;
&lt;li&gt;every renderer.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is why portability is not achieved merely by producing JSON.&lt;/p&gt;

&lt;p&gt;The JSON needs a precise semantic contract.&lt;/p&gt;

&lt;h2&gt;
  
  
  The same document must receive the same verdict
&lt;/h2&gt;

&lt;p&gt;A portable system can fail in subtle ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the JSON Schema accepts a document that the parser rejects;&lt;/li&gt;
&lt;li&gt;the parser accepts an operator that the evaluator does not implement;&lt;/li&gt;
&lt;li&gt;the runtime validates a value differently from a backend SDK;&lt;/li&gt;
&lt;li&gt;one renderer treats a field as disabled while another still submits it;&lt;/li&gt;
&lt;li&gt;a visual editor generates an option that one client silently discards.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every component can look correct in isolation while the whole system remains wrong.&lt;/p&gt;

&lt;p&gt;While building Modyra, I started treating these relationships as executable claims:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A version accepted by one runtime has a defined
position in every other runtime.

A disabled positional row does not move the rows
that follow it.

A value that survives a JSON round trip preserves
the same meaning.

A renderer does not invent behavior that the
contract did not declare.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The project uses differential, conformance and adversarial tests to try to falsify these claims.&lt;/p&gt;

&lt;p&gt;The purpose is not merely to make a test suite green.&lt;/p&gt;

&lt;p&gt;The purpose is to discover when two entrances into the same system tell different stories.&lt;/p&gt;

&lt;h2&gt;
  
  
  Remote form documents are untrusted input
&lt;/h2&gt;

&lt;p&gt;A server-supplied form is still external input.&lt;/p&gt;

&lt;p&gt;Even if the server belongs to the same organization, the document might have been produced by a CMS, visual editor, migration, third-party service or generative model.&lt;/p&gt;

&lt;p&gt;A form document can be harmful without containing executable code.&lt;/p&gt;

&lt;p&gt;For example, this small regular expression can freeze a JavaScript thread:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(a+)+$
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A near-matching input can trigger catastrophic backtracking.&lt;/p&gt;

&lt;p&gt;Because JavaScript regular-expression matching is synchronous, the entire interface can stop processing keystrokes and repainting.&lt;/p&gt;

&lt;p&gt;Other hostile or malformed documents can contain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;excessive nesting;&lt;/li&gt;
&lt;li&gt;unsafe property paths;&lt;/li&gt;
&lt;li&gt;unsupported operators;&lt;/li&gt;
&lt;li&gt;recursive dependencies;&lt;/li&gt;
&lt;li&gt;invalid widget kinds;&lt;/li&gt;
&lt;li&gt;oversized collections;&lt;/li&gt;
&lt;li&gt;ambiguous contract versions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A portable contract therefore needs more than deserialization.&lt;/p&gt;

&lt;p&gt;It needs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;structural validation;&lt;/li&gt;
&lt;li&gt;explicit limits;&lt;/li&gt;
&lt;li&gt;closed vocabularies;&lt;/li&gt;
&lt;li&gt;safe path handling;&lt;/li&gt;
&lt;li&gt;deterministic diagnostics;&lt;/li&gt;
&lt;li&gt;version negotiation;&lt;/li&gt;
&lt;li&gt;no dynamic code execution.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If one validator is unsafe to execute, the field itself should not necessarily disappear.&lt;/p&gt;

&lt;p&gt;The engine can preserve the field, refuse the unsafe rule and report the degradation.&lt;/p&gt;

&lt;p&gt;That is a better failure mode than making the form unusable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Portability has a real cost
&lt;/h2&gt;

&lt;p&gt;This architecture is not appropriate for every form.&lt;/p&gt;

&lt;p&gt;A local login form does not need a versioned remote contract, multiple renderers, backend parsers and a conformance suite.&lt;/p&gt;

&lt;p&gt;A focused framework-native library will usually be simpler and more appropriate.&lt;/p&gt;

&lt;p&gt;A contract-driven approach becomes valuable when forms are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;supplied through APIs;&lt;/li&gt;
&lt;li&gt;generated by tooling;&lt;/li&gt;
&lt;li&gt;shared across applications;&lt;/li&gt;
&lt;li&gt;frequently changed;&lt;/li&gt;
&lt;li&gt;deeply nested;&lt;/li&gt;
&lt;li&gt;required to behave consistently across runtimes;&lt;/li&gt;
&lt;li&gt;validated independently by a backend;&lt;/li&gt;
&lt;li&gt;governed as long-lived business artifacts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Portability does not eliminate complexity.&lt;/p&gt;

&lt;p&gt;It relocates complexity away from every consuming application and into shared infrastructure.&lt;/p&gt;

&lt;p&gt;That trade only makes sense when several consumers would otherwise rebuild the same machinery independently.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I am building
&lt;/h2&gt;

&lt;p&gt;Modyra currently explores this model through:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a typed, framework-independent form engine;&lt;/li&gt;
&lt;li&gt;nested positional and keyed collections;&lt;/li&gt;
&lt;li&gt;synchronous, asynchronous and cross-field validation;&lt;/li&gt;
&lt;li&gt;cancellable server validation;&lt;/li&gt;
&lt;li&gt;draft persistence and undo/redo;&lt;/li&gt;
&lt;li&gt;a validated Dynamic Form Contract;&lt;/li&gt;
&lt;li&gt;Angular, React, Vue, Lit, Solid, Preact and Svelte integrations;&lt;/li&gt;
&lt;li&gt;complete renderers for Angular, Lit and plain HTML;&lt;/li&gt;
&lt;li&gt;Studio and code-generation tooling;&lt;/li&gt;
&lt;li&gt;Java and Rust contract parsers;&lt;/li&gt;
&lt;li&gt;renderer conformance and adversarial testing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Not every part has reached the same maturity.&lt;/p&gt;

&lt;p&gt;The core contract is currently the strongest part of the project. Some adapters, Studio and rendering capabilities are still evolving.&lt;/p&gt;

&lt;p&gt;The project publishes known gaps instead of presenting every surface as complete.&lt;/p&gt;

&lt;p&gt;That honesty matters because the architecture makes an ambitious promise:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;One form contract should preserve its meaning wherever it is interpreted.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A promise like that should be difficult to earn.&lt;/p&gt;

&lt;h2&gt;
  
  
  The question I am still exploring
&lt;/h2&gt;

&lt;p&gt;The technical question is no longer whether forms can be represented as data.&lt;/p&gt;

&lt;p&gt;Many systems have already demonstrated that they can.&lt;/p&gt;

&lt;p&gt;The more interesting question is this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;How much behavior can become portable data before the contract becomes more difficult than the application code it replaces?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;My current answer is that the canonical contract can remain explicit and rigorous while the TypeScript authoring API becomes significantly smaller.&lt;/p&gt;

&lt;p&gt;The user should write the concise form.&lt;/p&gt;

&lt;p&gt;The engine should absorb the difficult parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;normalization;&lt;/li&gt;
&lt;li&gt;path resolution;&lt;/li&gt;
&lt;li&gt;capability checks;&lt;/li&gt;
&lt;li&gt;versioning;&lt;/li&gt;
&lt;li&gt;diagnostics;&lt;/li&gt;
&lt;li&gt;runtime limits;&lt;/li&gt;
&lt;li&gt;renderer conformance.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In other words:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Keep the public syntax small. Keep the internal contract complete.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Try it and challenge the design
&lt;/h2&gt;

&lt;p&gt;Modyra is open source, and I am particularly interested in criticism from developers who have built:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;server-driven forms;&lt;/li&gt;
&lt;li&gt;schema-based interfaces;&lt;/li&gt;
&lt;li&gt;enterprise workflows;&lt;/li&gt;
&lt;li&gt;custom form builders;&lt;/li&gt;
&lt;li&gt;accessible component systems;&lt;/li&gt;
&lt;li&gt;multi-framework libraries.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can explore the project here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://modyra.github.io/modyra/" rel="noopener noreferrer"&gt;Modyra documentation and demos&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/modyra/modyra" rel="noopener noreferrer"&gt;Modyra source code&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I would genuinely like to know:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Is a portable form contract a problem you have encountered?&lt;/li&gt;
&lt;li&gt;Which part of this architecture feels essential?&lt;/li&gt;
&lt;li&gt;Which part feels unnecessarily complex?&lt;/li&gt;
&lt;li&gt;What would prevent you from using this approach?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I am the author, so I am obviously invested in the idea.&lt;/p&gt;

&lt;p&gt;That is exactly why I am looking for arguments that can break it.&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>opensource</category>
      <category>webdev</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Just shipped @modyra/core: a tiny state layer for complex frontends</title>
      <dc:creator>Lorenzo Muscherà</dc:creator>
      <pubDate>Mon, 20 Jul 2026 12:35:35 +0000</pubDate>
      <link>https://dev.to/lorenzomuschera/just-shipped-modyracore-a-tiny-state-layer-for-complex-frontends-3k4n</link>
      <guid>https://dev.to/lorenzomuschera/just-shipped-modyracore-a-tiny-state-layer-for-complex-frontends-3k4n</guid>
      <description>&lt;p&gt;I've just published a small npm package called &lt;a href="https://www.npmjs.com/package/@modyra/core" rel="noopener noreferrer"&gt;@modyra/core&lt;/a&gt; that came out of a very specific pain: handling "slightly complex" app state in frontend projects without ending up with a mess of context, custom hooks and scattered stores.&lt;/p&gt;

&lt;p&gt;The focus of the package is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Minimal API, all in TypeScript, no hidden magic&lt;/li&gt;
&lt;li&gt;No heavy runtime: just tools to model the core of your
app as a set of state modules&lt;/li&gt;
&lt;li&gt;Designed to plug into React/Angular/vanilla JS without
forcing you to rewrite everything&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The idea is to treat your app's domain as a set of "core units" with clear responsibilities: each unit exposes state, actions and rules, and the rest of the app simply consumes them. It came out of a few projects where Redux/Zustand/signals etc. were fine, but started to feel either too verbose or not very close to the actual business domain.&lt;/p&gt;

&lt;p&gt;If you feel like taking a look and tearing it apart, feedback (including harsh ones) is very welcome: naming, API design, examples - everything is still fresh enough to change. And if you think the approach is worth exploring, a star on &lt;a href="https://github.com/modyra/modyra" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; would really help me keep pushing the project forward.&lt;/p&gt;

</description>
      <category>frontend</category>
      <category>javascript</category>
      <category>opensource</category>
      <category>typescript</category>
    </item>
  </channel>
</rss>
