<?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: numq</title>
    <description>The latest articles on DEV Community by numq (@numq).</description>
    <link>https://dev.to/numq</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%2F3404309%2Ff841070b-c50e-4f33-947f-32a110d186ff.jpg</url>
      <title>DEV Community: numq</title>
      <link>https://dev.to/numq</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/numq"/>
    <language>en</language>
    <item>
      <title>Kempo: Real-Time Audio Time-Stretching, Pitch-Shifting, and Formant Control in Pure Kotlin Multiplatform</title>
      <dc:creator>numq</dc:creator>
      <pubDate>Wed, 14 Oct 2026 07:00:21 +0000</pubDate>
      <link>https://dev.to/numq/kempo-real-time-audio-time-stretching-pitch-shifting-and-formant-control-in-pure-kotlin-4ok3</link>
      <guid>https://dev.to/numq/kempo-real-time-audio-time-stretching-pitch-shifting-and-formant-control-in-pure-kotlin-4ok3</guid>
      <description>&lt;h3&gt;
  
  
  Zero-allocation streaming, phase-locked clarity, and multichannel spatial coherence across JVM, Android, iOS, and WebAssembly.
&lt;/h3&gt;

&lt;p&gt;Whether you are building a podcast player with variable playback speed, a music practice app that slows down a complex guitar solo without dropping the tuning, a real-time voice modulator, or a spatial audio engine for a game, you eventually hit the same fundamental challenge: &lt;strong&gt;decoupling time from pitch&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In raw digital audio, playback speed and pitch are locked together. Play a buffer twice as fast, and the pitch jumps up a full octave. Slow it down, and everything drags into a muddy baritone.&lt;/p&gt;

&lt;p&gt;Breaking that link—while keeping drums punchy, vocals natural, and the stereo image intact—requires a dedicated Digital Signal Processing (DSP) engine.&lt;/p&gt;

&lt;p&gt;Meet &lt;strong&gt;&lt;a href="https://github.com/numq/kempo" rel="noopener noreferrer"&gt;Kempo&lt;/a&gt;&lt;/strong&gt; (&lt;code&gt;io.github.numq:kempo&lt;/code&gt;): an open-source, pure Kotlin Multiplatform audio time-stretching and pitch-shifting library built for studio-grade acoustic transparency and sub-millisecond real-time streaming across &lt;strong&gt;JVM, Android, iOS, and WebAssembly (Wasm/JS)&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Kempo Brings to Your Audio Stack
&lt;/h2&gt;

&lt;p&gt;Kempo is designed to be drop-in simple for high-level application code while exposing full, granular control for low-level audio threads:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Independent Time, Pitch, and Formant Control:&lt;/strong&gt; Speed up or slow down audio (&lt;code&gt;0.25x&lt;/code&gt; to &lt;code&gt;3.0x&lt;/code&gt; and beyond), transpose musical pitch in semitones, and shift vocal timbre independently.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero-Allocation Real-Time Streaming:&lt;/strong&gt; Once configured, the block-by-block processing loop allocates &lt;strong&gt;0 bytes on the heap&lt;/strong&gt;, eliminating Garbage Collection pauses on high-priority audio threads.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vertical Harmonic Phase-Locking:&lt;/strong&gt; Keeps transients crisp and eliminates the hollow, "underwater" phasing artifacts common in basic time-stretchers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multichannel &amp;amp; 5.1 Surround Coherence:&lt;/strong&gt; Preserves exact inter-channel phase relationships across stereo and surround tracks so the soundstage never collapses.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sample-Accurate Offline Rendering:&lt;/strong&gt; Built-in pre-roll warm-up, algorithmic latency compensation, and automatic overlap-add tail flushing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Built-In PCM-16 Serialization:&lt;/strong&gt; Convert raw little-endian or big-endian WAV/PCM byte arrays directly into normalized floating-point tracks and back.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  How Kempo Keeps Audio Crisp (Without the Artifacts)
&lt;/h2&gt;

&lt;p&gt;If you have ever used a naive time-stretching algorithm, you probably recognize three classic audio defects: smeared drum hits, "chipmunk" vocals, and a collapsing stereo field. Kempo solves all three at the engine level.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Eliminating Transient Smearing with Vertical Phase-Locking
&lt;/h3&gt;

&lt;p&gt;Standard phase vocoders split audio into hundreds of narrow frequency bins and advance the phase of each bin over time. Because each bin is updated in isolation, the natural harmonic alignment between a fundamental note and its overtones quickly drifts apart. Percussive attacks lose their snap, and instruments sound phasey and diffuse.&lt;/p&gt;

&lt;p&gt;Kempo continuously analyzes the smoothed spectral energy contour of each frame to locate prominent spectral peaks. Instead of letting neighboring bins drift, Kempo performs &lt;strong&gt;vertical phase-locking&lt;/strong&gt;—anchoring the phase trajectories of surrounding frequency bins to their dominant harmonic peaks. Harmonics move together as a unified acoustic event, keeping transients tight.&lt;/p&gt;

&lt;p&gt;When stretching audio to extreme lengths (beyond &lt;code&gt;2.0x&lt;/code&gt;), Kempo automatically transitions into controlled stochastic phase dispersion, preventing the metallic ringing that usually plagues ultra-slow playback.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Fixing the "Chipmunk" Effect with Formant Decoupling
&lt;/h3&gt;

&lt;p&gt;When you pitch a human voice up by +5 semitones, you aren't just raising the pitch of the vocal cords—you are also shrinking the perceived size of the singer's vocal tract. The result is the notorious "chipmunk" effect (or the "monster" effect when pitching down).&lt;/p&gt;

&lt;p&gt;In Kempo, pitch and vocal timbre (formants) are decoupled. Kempo extracts the spectral envelope of the signal and allows you to shift or lock formants independently of the musical transposition:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Natural Pitch Shifting:&lt;/strong&gt; Enable formant compensation (&lt;code&gt;compensatePitch = true&lt;/code&gt;) to keep a singer's vocal tract resonance anchored while changing their melody.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Creative Voice Design:&lt;/strong&gt; Shift formants up or down while keeping pitch at &lt;code&gt;0 st&lt;/code&gt; to transform the character and age of a voice without changing the note being sung.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Preserving Stereo Width and 5.1 Surround Imaging
&lt;/h3&gt;

&lt;p&gt;Our ears localize sound in space by detecting tiny timing and phase differences between the left and right channels. If an audio processor stretches the Left and Right channels independently, their relative phases drift apart—causing the stereo image to wobble and center-panned vocals to hollow out when downmixed.&lt;/p&gt;

&lt;p&gt;Kempo processes multichannel audio coherently. At every frequency band, it identifies the dominant channel and locks the Inter-channel Phase Differences (IPD) of all remaining channels to that reference. Whether you are processing a stereo master or a discrete 6-channel 5.1 surround mix, panning stays locked and center dialogue remains phase-aligned.&lt;/p&gt;




&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;Kempo is published on Maven Central. Add the dependency to your &lt;code&gt;build.gradle.kts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nf"&gt;kotlin&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;sourceSets&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;commonMain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dependencies&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;implementation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"io.github.numq:kempo:1.0.0"&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;Or for a single-platform JVM/Android module:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nf"&gt;dependencies&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;implementation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"io.github.numq:kempo:1.0.0"&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;h3&gt;
  
  
  1. The One-Liner: Offline Track Processing
&lt;/h3&gt;

&lt;p&gt;If you have raw 16-bit PCM bytes (from a WAV file, network stream, or microphone recording) and want to stretch or transpose them in a background coroutine, &lt;code&gt;PcmConverter&lt;/code&gt; and &lt;code&gt;AudioTrack&lt;/code&gt; handle the entire pipeline in a few lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;io.github.numq.kempo.PcmConverter&lt;/span&gt;

&lt;span class="c1"&gt;// 1. Decode interleaved 16-bit PCM bytes into a normalized [-1.0f, 1.0f] AudioTrack&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;track&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;PcmConverter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromPcm16&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;bytes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rawWavBytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;channels&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;sampleRate&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;44100f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;isLittleEndian&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// 2. Speed up by 25%, pitch up by 3 semitones, keep natural formants&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;stretchedTrack&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;track&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stretch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;timeRatio&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.25f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;pitchSemitones&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;3.0f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;formantSemitones&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.0f&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// 3. Convert back to 16-bit PCM bytes for playback or file export&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;outputBytes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;PcmConverter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toPcm16&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stretchedTrack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;isLittleEndian&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Zero-Allocation Real-Time Streaming
&lt;/h3&gt;

&lt;p&gt;Real-time audio callbacks operate under strict deadlines. Allocating arrays or objects inside the render loop will eventually trigger the garbage collector and cause audible clicks.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;KempoStretch&lt;/code&gt; pre-allocates all ring buffers, FFT plans, twiddle factors, and spectral workspaces upfront during &lt;code&gt;presetDefault()&lt;/code&gt; or &lt;code&gt;configure()&lt;/code&gt;. Inside your audio callback, &lt;code&gt;process()&lt;/code&gt; mutates pre-allocated buffers in place:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;io.github.numq.kempo.KempoStretch&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;kotlin.math.max&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LiveAudioProcessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;channels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;sampleRate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Float&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;48000f&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;stretch&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;KempoStretch&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;presetDefault&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;channels&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sampleRate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;outBlockSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stretch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;defaultInterval&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;outBuffer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;channels&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;FloatArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;outBlockSize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;updateParameters&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pitchSemitones&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;formantSemitones&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;stretch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setTransposeSemitones&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pitchSemitones&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;stretch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setFormantSemitones&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;formantSemitones&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;compensatePitch&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Safe for high-priority audio threads: 0 B heap allocations&lt;/span&gt;
    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;processFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;inputChannels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;FloatArray&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;,&lt;/span&gt;
        &lt;span class="n"&gt;playbackSpeed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Float&lt;/span&gt;
    &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;FloatArray&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;inSamples&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;outBlockSize&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;playbackSpeed&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toInt&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="n"&gt;stretch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;inputs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;inputChannels&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;inputSamples&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;inSamples&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;outputs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;outBuffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;outputSamples&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;outBlockSize&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;outBuffer&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;Need to support interactive scrubbing on a timeline without clicks or phase resets? Use &lt;code&gt;stretch.seek()&lt;/code&gt; before resuming playback to pre-seed the internal phase accumulators with the audio frames right before the new cursor position:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;seekPreRoll&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;minOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stretch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;seekLength&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;targetSampleIndex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;stretch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;seek&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;inputs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;track&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;channels&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;inputSamples&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;seekPreRoll&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;playbackRate&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;inputOffset&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;targetSampleIndex&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;seekPreRoll&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;coerceAtLeast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&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;h3&gt;
  
  
  3. Sample-Accurate Offline Rendering with &lt;code&gt;exact()&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;When rendering an audio clip for a DAW timeline or a game asset pipeline, you often need the output buffer to match an exact target sample count—without leading silence from the STFT window latency or cut-off reverb tails at the end.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;KempoStretch.exact()&lt;/code&gt; automates the entire offline lifecycle: it calculates the required pre-roll (&lt;code&gt;outputSeekLength&lt;/code&gt;), warms up the vocoder state (&lt;code&gt;outputSeek&lt;/code&gt;), processes the body of the track, and flushes the final synthesis tails (&lt;code&gt;flush&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;io.github.numq.kempo.KempoStretch&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;kotlin.math.roundToInt&lt;/span&gt;

&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;renderExactClip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;FloatArray&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;,&lt;/span&gt;
    &lt;span class="n"&gt;sampleRate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;timeRatio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;pitchSemitones&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Float&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;FloatArray&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;channels&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;inSamples&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;outSamples&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inSamples&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="n"&gt;timeRatio&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;roundToInt&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;coerceAtLeast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;output&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;channels&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;FloatArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;outSamples&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;stretch&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;KempoStretch&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;presetDefault&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;channels&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sampleRate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;setTransposeSemitones&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pitchSemitones&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;stretch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exact&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;inputs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;inputSamples&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;inSamples&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;outputs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;outputSamples&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;outSamples&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Custom Frequency Mapping &amp;amp; Tonality Limits
&lt;/h3&gt;

&lt;p&gt;For experimental sound design, spectral effects, or specialized speech conditioning, Kempo lets you bypass standard semitone math entirely:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tonality Limits:&lt;/strong&gt; When transposing speech or vocals up, multiplying high-frequency sibilance ("s" and "sh" sounds) can make them sound lispy or artificial. Passing a &lt;code&gt;tonalityLimit&lt;/code&gt; to &lt;code&gt;setTransposeSemitones(semitones, tonalityLimit)&lt;/code&gt; transitions the upper spectrum from multiplicative harmonic scaling to a clean linear shift.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom Transfer Functions:&lt;/strong&gt; Pass any lambda to &lt;code&gt;setFreqMap&lt;/code&gt; to warp normalized frequencies &lt;code&gt;[0.0f, 0.5f]&lt;/code&gt; non-linearly:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="n"&gt;stretch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setFreqMap&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;normalizedFreq&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="c1"&gt;// Custom non-linear spectral warping&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;normalizedFreq&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;1.15f&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;coerceIn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.5f&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;h2&gt;
  
  
  Engineered for Speed in Pure Kotlin
&lt;/h2&gt;

&lt;p&gt;Writing high-throughput DSP in pure Kotlin without falling back to platform-specific C/C++ libraries requires careful attention to memory layout and CPU cache locality:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Flat Power-of-Two Ring Buffers (&lt;code&gt;MultiChannelBuffer&lt;/code&gt;):&lt;/strong&gt; Instead of nested object arrays or modulo arithmetic, Kempo backs all channels with a single flat &lt;code&gt;FloatArray&lt;/code&gt;. Wrap-around indices are resolved via bitwise masks (&lt;code&gt;(head + offset) and mask&lt;/code&gt;), and overlap-add accumulation uses 4x unrolled loops.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;In-Place Mixed-Radix FFT (&lt;code&gt;FastFft&lt;/code&gt; &amp;amp; &lt;code&gt;ModifiedRealFFT&lt;/code&gt;):&lt;/strong&gt; Supports radix-2, radix-3, radix-4, and composite window sizes with precomputed twiddle tables. Real signals are evaluated on a half-bin shifted grid &lt;code&gt;(k + 0.5) / N&lt;/code&gt;, avoiding DC and Nyquist edge singularities and cutting transform overhead in half.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Split Computation Mode (&lt;code&gt;split = true&lt;/code&gt;):&lt;/strong&gt; In real-time audio, doing a full STFT analysis and synthesis every &lt;code&gt;N&lt;/code&gt; samples can cause periodic CPU spikes. Enabling &lt;code&gt;split = true&lt;/code&gt; in &lt;code&gt;configure()&lt;/code&gt; or &lt;code&gt;presetCheaper()&lt;/code&gt; slices the spectral workload into small incremental steps distributed evenly across every sample frame.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automatic Silence Bypass:&lt;/strong&gt; When input energy drops below the noise floor, Kempo automatically bypasses heavy spectral math while keeping ring buffers synchronized.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In JMH benchmarks on standard desktop JVM hardware (44.1 kHz audio), Kempo processes a real-time streaming block in &lt;strong&gt;~220 μs (mono)&lt;/strong&gt; and &lt;strong&gt;~484 μs (stereo)&lt;/strong&gt;, and renders offline audio at &lt;strong&gt;over 128x real-time speed&lt;/strong&gt; for mono and &lt;strong&gt;~70x real-time&lt;/strong&gt; for stereo.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try It Live: The Compose Desktop Studio App
&lt;/h2&gt;

&lt;p&gt;Want to hear how Kempo handles your own audio files before writing a line of code?&lt;/p&gt;

&lt;p&gt;The repository includes an interactive &lt;strong&gt;Compose Multiplatform Desktop Studio&lt;/strong&gt; application (&lt;code&gt;:example&lt;/code&gt;). Drop any &lt;code&gt;.wav&lt;/code&gt; file onto the window to inspect its waveform, scrub through the timeline in real time, and tweak live parameters on the fly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Playback Speed (&lt;code&gt;0.25x&lt;/code&gt; – &lt;code&gt;3.00x&lt;/code&gt;)&lt;/strong&gt; with instant presets (&lt;code&gt;0.50x&lt;/code&gt;, &lt;code&gt;0.75x&lt;/code&gt;, &lt;code&gt;1.25x&lt;/code&gt;, &lt;code&gt;1.50x&lt;/code&gt;, &lt;code&gt;2.00x&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pitch Shift (&lt;code&gt;-12 st&lt;/code&gt; to &lt;code&gt;+12 st&lt;/code&gt;)&lt;/strong&gt; in semitone steps&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Formant Shift (&lt;code&gt;-12 st&lt;/code&gt; to &lt;code&gt;+12 st&lt;/code&gt;)&lt;/strong&gt; for real-time vocal timbre shaping&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Explore the&amp;nbsp;Code
&lt;/h2&gt;

&lt;p&gt;Check out the full source code, architecture docs, and the runnable CLI attack simulation on &lt;a href="https://github.com/numq/kempo" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>kotlin</category>
      <category>kmp</category>
      <category>softwareengineering</category>
      <category>audioengineering</category>
    </item>
    <item>
      <title>Why HTTPS Won’t Save Your Checkout Flow: Building Zero-Trust State Machines in Kotlin Multiplatform</title>
      <dc:creator>numq</dc:creator>
      <pubDate>Wed, 14 Oct 2026 06:26:49 +0000</pubDate>
      <link>https://dev.to/numq/why-https-wont-save-your-checkout-flow-building-zero-trust-state-machines-in-kotlin-multiplatform-3g5f</link>
      <guid>https://dev.to/numq/why-https-wont-save-your-checkout-flow-building-zero-trust-state-machines-in-kotlin-multiplatform-3g5f</guid>
      <description>&lt;h3&gt;
  
  
  How to protect multi-step workflows, fintech funnels, and Server-Driven UI from step-skipping, state tampering, and replay attacks — with zero runtime dependencies.
&lt;/h3&gt;

&lt;p&gt;Imagine a standard e-commerce checkout flow: &lt;strong&gt;Shopping Cart → Shipping Address → Promo Code → Payment Authorization&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You use HTTPS. You validate JWT tokens on every request. Your backend endpoints are covered with unit tests. You feel safe.&lt;/p&gt;

&lt;p&gt;Then someone opens your app on a rooted device, hooks into memory with Frida, or simply manipulates an internal deep link. Instead of going from &lt;strong&gt;Cart&lt;/strong&gt; to &lt;strong&gt;Shipping&lt;/strong&gt; (where a $50 express delivery fee is calculated), the client dispatches a transition straight from &lt;strong&gt;Cart&lt;/strong&gt; to &lt;strong&gt;Payment&lt;/strong&gt;. Or worse, in a Server-Driven UI (SDUI) flow, an attacker replays a cached JSON payload from five minutes ago where a KYC verification flag was temporarily set to &lt;code&gt;true&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;HTTPS only encrypts the pipe. JWTs only verify who is holding the token.&lt;/strong&gt; Neither guarantees that your client-side state machine actually followed the legal sequence of steps to reach its current screen.&lt;/p&gt;

&lt;p&gt;To fix this at the architectural level without littering business logic with endless &lt;code&gt;if-else&lt;/code&gt; checks, I built &lt;strong&gt;TrustChain&lt;/strong&gt; — a pure functional, cryptographically verifiable state machine ledger for &lt;strong&gt;Kotlin Multiplatform (KMP)&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Mental Model: Think "Git Commits" for Your App State
&lt;/h2&gt;

&lt;p&gt;You don’t need a PhD in cryptography or heavy blockchain consensus algorithms to secure a user journey. You just need &lt;strong&gt;strict lineage&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Think about how Git works: every commit contains a hash of its own content plus the exact hash of the parent commit. If you try to alter history or insert a commit out of nowhere, the hashes don't match, and Git rejects the tree immediately.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TrustChain brings this exact mechanism to your application’s navigation and state stack:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Every screen or workflow step has a unique &lt;code&gt;hash&lt;/code&gt; representing its current state, and a &lt;code&gt;previousHash&lt;/code&gt; pointing to the exact step that preceded it.&lt;/li&gt;
&lt;li&gt;When the user moves forward, the incoming step’s &lt;code&gt;previousHash&lt;/code&gt; &lt;strong&gt;must&lt;/strong&gt; match the &lt;code&gt;hash&lt;/code&gt; of the currently active step on top of the stack.&lt;/li&gt;
&lt;li&gt;Partial state updates (like applying a promo code or updating live order status over WebSockets) arrive as &lt;strong&gt;signed deltas&lt;/strong&gt; (&lt;code&gt;DeltaBlock&lt;/code&gt;). They verify the current hash, check the server’s asymmetric digital signature, mutate the state, and verify the new hash.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If an attacker tries to skip a screen, inject a fake discount, or replay an old state, the hash chain breaks. The state machine fails fast before a single pixel is rendered or a network call is fired.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Security Concern&lt;/th&gt;
&lt;th&gt;Standard REST + HTTPS&lt;/th&gt;
&lt;th&gt;JWT Tokens&lt;/th&gt;
&lt;th&gt;TrustChain&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Data In-Transit Encryption&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes (TLS)&lt;/td&gt;
&lt;td&gt;Yes (TLS)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Yes (TLS)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Prevent Step Skipping (Deep Links)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No (Complex backend checks)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Yes (Parent Hash Check)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Detect Tampered State Deltas&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Partially&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Yes (Asymmetric Signatures)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Replay Attack Prevention&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Manual nonces required&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Yes (Strict Hash Lineage)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Zero Runtime Dependencies&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;Often heavy&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Yes (Pure KMP)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Designing for Zero Bloat: The Core Primitives
&lt;/h2&gt;

&lt;p&gt;One of my main goals with &lt;code&gt;TrustChain&lt;/code&gt; was keeping the core completely free of third-party runtime dependencies so it drops cleanly into Android, iOS, Desktop, Web, or backend KMP targets.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. &lt;code&gt;VerifiableBlock&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Any screen, workflow step, or state snapshot in your domain simply implements a three-property interface:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;VerifiableBlock&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;previousHash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="c1"&gt;// null only for the root (genesis) step&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. &lt;code&gt;DeltaBlock&lt;/code&gt; (Signed Partial Updates)
&lt;/h3&gt;

&lt;p&gt;Why re-fetch an entire screen when a user applies a 10% coupon or an order changes status? Instead of sending full state trees, the server sends a lightweight &lt;code&gt;DeltaBlock&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Serializable&lt;/span&gt;
&lt;span class="kd"&gt;data class&lt;/span&gt; &lt;span class="nc"&gt;DeltaBlock&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;P&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
    &lt;span class="nd"&gt;@SerialName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"patches"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;patches&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;P&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nd"&gt;@SerialName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"previous_hash"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;previousHash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nd"&gt;@SerialName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"new_hash"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;newHash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nd"&gt;@SerialName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"signature"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice how &lt;code&gt;DeltaBlock&lt;/code&gt; locks the mutation from both sides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It can &lt;strong&gt;only&lt;/strong&gt; be applied if the client is currently sitting on &lt;code&gt;previousHash&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Its &lt;code&gt;newHash&lt;/code&gt; is signed by the server's private key (&lt;code&gt;RSA&lt;/code&gt;, &lt;code&gt;Ed25519&lt;/code&gt;, or &lt;code&gt;ECDSA&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Once the client applies &lt;code&gt;patches&lt;/code&gt; via a &lt;code&gt;BlockPatcher&lt;/code&gt;, the resulting block must produce &lt;code&gt;newHash&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Pure Functional Reduction
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;TrustChain&lt;/code&gt; is completely immutable. There are no hidden side effects, mutable singletons, or uncaught exceptions flying across coroutines. You send a &lt;code&gt;ChainEvent&lt;/code&gt; (&lt;code&gt;Push&lt;/code&gt;, &lt;code&gt;Pop&lt;/code&gt;, &lt;code&gt;Replace&lt;/code&gt;, or &lt;code&gt;ApplyDeltas&lt;/code&gt;), and &lt;code&gt;reduce()&lt;/code&gt; returns a standard Kotlin &lt;code&gt;Result&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;ChainEvent&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="nc"&gt;B&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;VerifiableBlock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="nc"&gt;P&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;data class&lt;/span&gt; &lt;span class="nc"&gt;Push&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;B&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;VerifiableBlock&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;block&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;B&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ChainEvent&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;B&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Nothing&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="kd"&gt;object&lt;/span&gt; &lt;span class="nc"&gt;Pop&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ChainEvent&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Nothing&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Nothing&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="kd"&gt;data class&lt;/span&gt; &lt;span class="nc"&gt;Replace&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;B&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;VerifiableBlock&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;block&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;B&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ChainEvent&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;B&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Nothing&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="kd"&gt;data class&lt;/span&gt; &lt;span class="nc"&gt;ApplyDeltas&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;P&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;blocks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;DeltaBlock&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;P&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ChainEvent&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Nothing&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;P&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a batch of 5 deltas is applied and the 4th delta has a broken hash link or invalid signature, &lt;strong&gt;atomicity is preserved&lt;/strong&gt; — the entire reduction fails cleanly with a typed &lt;code&gt;ChainError&lt;/code&gt;, leaving your original &lt;code&gt;TrustChain&lt;/code&gt; untouched.&lt;/p&gt;




&lt;h2&gt;
  
  
  Real-World Walkthrough: The Unbreakable Checkout
&lt;/h2&gt;

&lt;p&gt;Let’s look at how this works in practice using a multi-step checkout flow.&lt;/p&gt;

&lt;p&gt;First, we define our domain step:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Serializable&lt;/span&gt;
&lt;span class="kd"&gt;data class&lt;/span&gt; &lt;span class="nc"&gt;CheckoutStep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;emptyMap&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;previousHash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;VerifiableBlock&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we initialize &lt;code&gt;TrustChain&lt;/code&gt; on the client. Notice how cryptography and state patching are injected via simple functional interfaces (&lt;code&gt;SignatureVerifier&lt;/code&gt; and &lt;code&gt;BlockPatcher&lt;/code&gt;). You aren't locked into any specific crypto library:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;clientChain&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TrustChain&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;CheckoutStep&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;(&lt;/span&gt;
    &lt;span class="n"&gt;strictMode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;verifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SignatureVerifier&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;signatureBase64&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="c1"&gt;// Plug in your platform's Ed25519 / RSA / ECDSA verifier&lt;/span&gt;
        &lt;span class="nc"&gt;CryptoEngine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;verify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;serverPublicKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;signatureBase64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;patcher&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;patches&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;newHash&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;patches&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;hash&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;newHash&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;h3&gt;
  
  
  The Legitimate User Journey
&lt;/h3&gt;

&lt;p&gt;As the user progresses through the checkout, every step returned by the server references the exact hash of the previous state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// 1. User opens the Shopping Cart (Genesis block, previousHash = null)&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;cartStep&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startCheckout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sessionId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;clientChain&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;clientChain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ChainEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cartStep&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;getOrThrow&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;// Active: Shopping Cart | Hash: h_c2c1a28598b8ad69&lt;/span&gt;

&lt;span class="c1"&gt;// 2. User confirms Shipping Address (Chained to Cart's hash)&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;shippingStep&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;submitShipping&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sessionId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Baker St 221B"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;clientChain&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;clientChain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ChainEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shippingStep&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;getOrThrow&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;// Active: Shipping &amp;amp; Address | Hash: h_50bda3ba68cced0d | Prev: h_c2c1a28598b8ad69&lt;/span&gt;

&lt;span class="c1"&gt;// 3. User applies a 10% Promo Code (Signed DeltaBlock mutation)&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;promoDelta&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;applyPromoCode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sessionId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"10%"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;clientChain&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;clientChain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ChainEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ApplyDeltas&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;listOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;promoDelta&lt;/span&gt;&lt;span class="p"&gt;))).&lt;/span&gt;&lt;span class="nf"&gt;getOrThrow&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;// Active: Shipping &amp;amp; Address (Patched) | New Total: $1890 | New Hash: h_6471c5f0c53a644b&lt;/span&gt;

&lt;span class="c1"&gt;// 4. User proceeds to Payment (Chained to the patched Shipping hash)&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;paymentStep&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;proceedToPayment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sessionId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;clientChain&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;clientChain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ChainEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;paymentStep&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;getOrThrow&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;// Active: Payment Authorization | Hash: h_6a6fd155e392f7ef&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Simulating the Attack: Skipping Shipping
&lt;/h3&gt;

&lt;p&gt;Now, let’s see what happens when an attacker loads the Cart (&lt;code&gt;h_c2c1a28598b8ad69&lt;/code&gt;) and tries to jump straight to the &lt;code&gt;paymentStep&lt;/code&gt; (bypassing shipping calculation):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;attackedChain&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TrustChain&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;CheckoutStep&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;strictMode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;attackedChain&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;attackedChain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ChainEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cartStep&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;getOrThrow&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;// Attacker tries to force-push the Payment step directly onto the Cart&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;attackResult&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;attackedChain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ChainEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;paymentStep&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="n"&gt;attackResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fold&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;onSuccess&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"VULNERABILITY: Attack succeeded!"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;onFailure&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nc"&gt;ChainError&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;HashMismatch&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nf"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"[!] BLOCKED BY TRUSTCHAIN: State machine integrity preserved."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="nf"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"    Expected parent: ${error.expected}, Actual: ${error.actual}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nc"&gt;ChainError&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SignatureVerificationFailed&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nf"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"[!] BLOCKED: Invalid server signature for block ${error.blockHash}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"[!] Blocked: ${error.message}"&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;&lt;strong&gt;Console Output:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;------------------------------------------------------
  Simulating Attack: Attacker tries to skip shipping  
------------------------------------------------------
Attacker has Cart loaded.
Attacker attempts to directly apply Payment step (bypassing Shipping)...
[!] BLOCKED BY TRUSTCHAIN: State machine integrity preserved.
Expected parent: h_c2c1a28598b8ad69, Actual: h_6471c5f0c53a644b

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because &lt;code&gt;paymentStep&lt;/code&gt; expects its parent to be the patched shipping step (&lt;code&gt;h_6471c5f0c53a644b&lt;/code&gt;), pushing it onto the cart (&lt;code&gt;h_c2c1a28598b8ad69&lt;/code&gt;) triggers an immediate &lt;code&gt;ChainError.HashMismatch&lt;/code&gt;. The state transition is rejected atomically.&lt;/p&gt;




&lt;h2&gt;
  
  
  Built for Developer Ergonomics
&lt;/h2&gt;

&lt;p&gt;Security tools often fail because they make local development painful. Nobody wants to spin up a signing server just to preview a Compose Multiplatform screen or write a quick UI test.&lt;/p&gt;

&lt;p&gt;That’s why &lt;code&gt;TrustChain&lt;/code&gt; includes a &lt;code&gt;strictMode&lt;/code&gt; flag:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;strictMode = true&lt;/code&gt; (Default / Production):&lt;/strong&gt; Enforces parent hash lineage on &lt;code&gt;Push&lt;/code&gt; and &lt;code&gt;Replace&lt;/code&gt;, verifies digital signatures via &lt;code&gt;SignatureVerifier&lt;/code&gt;, and ensures &lt;code&gt;BlockPatcher&lt;/code&gt; updates block hashes accurately.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;strictMode = false&lt;/code&gt; (Previews / UI Prototyping):&lt;/strong&gt; Bypasses cryptographic and lineage checks while keeping the exact same &lt;code&gt;ChainEvent&lt;/code&gt; reducer API, allowing you to mock and jump between screens freely during development.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Where Does TrustChain Shine?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Server-Driven UI (SDUI):&lt;/strong&gt; Guarantee that screens and dynamic patches pushed by the backend are rendered in the exact sequence intended by the server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;E-Commerce &amp;amp; Checkouts:&lt;/strong&gt; Make it impossible to bypass delivery calculation, tax steps, or terms acceptance before payment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fintech &amp;amp; Banking:&lt;/strong&gt; Enforce strict step ordering for wire transfers, credit limit increases, and multi-factor challenges.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;KYC &amp;amp; Onboarding Funnels:&lt;/strong&gt; Maintain a verifiable, tamper-proof progression through document capture and liveness checks.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Try It in Your Project
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;TrustChain&lt;/strong&gt; is open-source under the Apache 2.0 License and published directly to Maven Central.&lt;/p&gt;

&lt;p&gt;Check out the full source code, architecture docs, and the runnable CLI attack simulation on &lt;a href="https://github.com/numq/trustchain" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>kotlin</category>
      <category>cybersecurity</category>
      <category>mobile</category>
      <category>kmp</category>
    </item>
    <item>
      <title>Stop Trusting Your UI: Why Zero-Trust Server-Driven UI is the Next Industry Standard</title>
      <dc:creator>numq</dc:creator>
      <pubDate>Tue, 28 Jul 2026 11:55:41 +0000</pubDate>
      <link>https://dev.to/numq/stop-trusting-your-ui-why-zero-trust-server-driven-ui-is-the-next-industry-standard-13l2</link>
      <guid>https://dev.to/numq/stop-trusting-your-ui-why-zero-trust-server-driven-ui-is-the-next-industry-standard-13l2</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;TLS is not enough. Your JSON payloads are a ticking time bomb. Here is how Blueprint makes UI manipulation mathematically impossible - and why your compliance team will soon demand&amp;nbsp;it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Part 1: The Billion-Dollar Blind&amp;nbsp;Spot
&lt;/h2&gt;

&lt;p&gt;Server-Driven UI won. Airbnb, Spotify, and Uber proved that shipping UI from the backend gives you superpowers: instant A/B tests, zero-day fixes, and a single source of truth for business logic. The industry is racing to adopt it.&lt;/p&gt;

&lt;p&gt;But there's a problem nobody talks about at conferences: &lt;strong&gt;you've moved your business logic to the wire, but your security model is still "pray the TLS holds."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Skeptical? Think TLS is a silver bullet? In the real world, TLS is routinely bypassed or terminated early. SSL inspection on corporate devices (Enterprise MDM), compromised edge nodes or CDNs, malicious proxy extensions, and internal lateral movement within your own microservice architecture can all manipulate traffic while it's in plain text.&lt;/p&gt;

&lt;p&gt;This isn't theoretical. &lt;a href="https://owasp.org/www-project-mobile-top-10/" rel="noopener noreferrer"&gt;OWASP Mobile Top 10&lt;/a&gt; lists "Insufficient Cryptography" (M9) as a critical vulnerability. &lt;a href="https://csrc.nist.gov/pubs/sp/800/207/final" rel="noopener noreferrer"&gt;NIST SP 800–207&lt;/a&gt; defines Zero Trust Architecture as the gold standard. And &lt;a href="https://www.eba.europa.eu/regulation-and-policy/payment-services-and-electronic-money" rel="noopener noreferrer"&gt;PSD3&lt;/a&gt; is pushing financial apps toward cryptographic proof of transaction integrity - including the UI that initiates those transactions.&lt;/p&gt;

&lt;p&gt;Imagine this real-world attack:&lt;/p&gt;

&lt;p&gt;User taps: &lt;strong&gt;[ ☕ Buy Coffee - $5.00 ]&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  🔴 Traditional SDUI (Vulnerable)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Server&lt;/strong&gt; → MITM Proxy / Compromised CDN → &lt;strong&gt;Client&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Original payload: "action": &lt;code&gt;"api/charge?amount=5"&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Modified payload: "action": &lt;code&gt;"api/charge?amount=5000"&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The button still says "Buy Coffee"&lt;/li&gt;
&lt;li&gt;The user sees nothing suspicious&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Result: $5,000 leaves their account&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🟢 Blueprint Zero-Trust SDUI&amp;nbsp;(Immune)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Server&lt;/strong&gt; → [RSA Signature + Hash Chain] → &lt;strong&gt;Client&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Client checks: &lt;code&gt;incoming.previousHash == current.hash&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Client checks: &lt;code&gt;RSA.verify(newHash, signature)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Modified payload? → &lt;strong&gt;MISMATCH - rejected&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Replayed payload? → &lt;strong&gt;BROKEN CHAIN - rejected&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Result: UI refuses to render, attack neutralized&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The difference is mathematical proof versus blind faith.&lt;/strong&gt; And in regulated industries - FinTech, Healthcare, Enterprise - regulators are already asking the question: "How do you guarantee the UI your customer sees is exactly what your backend generated?"&lt;/p&gt;

&lt;p&gt;Blueprint is the answer. It's not just another SDUI framework. &lt;strong&gt;It's a cryptographic protocol for user interfaces.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 2: The Blueprint Chain - Your UI on a Blockchain
&lt;/h2&gt;

&lt;p&gt;Blueprint treats every screen as a block in an immutable chain. The client doesn't "request a screen." &lt;strong&gt;It validates a state transition.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Hash Chaining: Every Screen Knows Its&amp;nbsp;Parent
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Core data model - note the cryptographic fields&lt;/span&gt;
&lt;span class="nd"&gt;@Serializable&lt;/span&gt;
&lt;span class="kd"&gt;data class&lt;/span&gt; &lt;span class="nc"&gt;Blueprint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;emptyMap&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;root&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;BlueprintNode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;           &lt;span class="c1"&gt;// SHA-256 of this screen's content&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;previousHash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="c1"&gt;// Cryptographic link to previous screen&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the server pushes a new screen, the client executes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// BlueprintChain.kt - the zero-trust gatekeeper&lt;/span&gt;
&lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nc"&gt;ChainEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Push&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;strictMode&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;links&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isNotEmpty&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;currentHash&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;blueprint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;previousHash&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; 
            &lt;span class="nf"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;links&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;links&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;blueprint&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;right&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// ✅ Chain intact&lt;/span&gt;

        &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;ChainError&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;HashMismatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;expected&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;currentHash&lt;/span&gt; &lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="s"&gt;"null"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;actual&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;blueprint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;previousHash&lt;/span&gt;
        &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;left&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// 🚫 Chain broken&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;&lt;strong&gt;This is the gamechanger.&lt;/strong&gt; You cannot inject a phishing screen. You cannot replay an old "success" screen to hide an error. The chain must be unbroken, or the UI stops dead.&lt;/p&gt;

&lt;h3&gt;
  
  
  Performance That Does Not Sacrifice UX
&lt;/h3&gt;

&lt;p&gt;Security like this must be invisible to the user. Blueprint delivers:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Performance impact - imperceptible to users:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SHA-256 hashing (1 KB payload):&lt;/strong&gt; &amp;lt; 0.3 ms - negligible&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RSA-2048 signature verification:&lt;/strong&gt; &amp;lt; 2.1 ms - imperceptible&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Full chain push + verify:&lt;/strong&gt; &amp;lt; 3.8 ms - single frame at 60 FPS&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;State delta apply + verify:&lt;/strong&gt; &amp;lt; 2.5 ms - sub-frame&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All verification happens locally. No network calls. No server round-trips. The chain validates on-device, ensuring zero latency impact on navigation.&lt;/p&gt;

&lt;h3&gt;
  
  
  State Deltas with RSA Signatures: Surgical, Signed&amp;nbsp;Updates
&lt;/h3&gt;

&lt;p&gt;Full screen reloads are wasteful. Blueprint uses StateDeltaBlock for surgical state patches:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Serializable&lt;/span&gt;
&lt;span class="kd"&gt;data class&lt;/span&gt; &lt;span class="nc"&gt;StateDeltaBlock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;patches&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// Only the changed key-value pairs&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;previousHash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c1"&gt;// Must match current state hash&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;newHash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;               &lt;span class="c1"&gt;// Hash of the new state&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;              &lt;span class="c1"&gt;// RSA-signed by server's private key&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The verification is absolute:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="n"&gt;strictMode&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;verifier&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="n"&gt;verifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;verify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;newHash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;signatureBase64&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;signature&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;ChainError&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SignatureVerificationFailed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;blockHash&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;newHash&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;left&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Even if an attacker compromises your CDN&lt;/strong&gt;, they cannot forge a valid &lt;code&gt;StateDeltaBlock&lt;/code&gt;. The RSA private key lives in your Hardware Security Module or Vault. The client holds only the public key. Without a valid signature, the state machine returns &lt;code&gt;Either.Left(ChainError)&lt;/code&gt; - a type-safe rejection that never crashes the app, but definitively blocks the tampered update.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key rotation is built in.&lt;/strong&gt; The &lt;code&gt;SignatureVerifier&lt;/code&gt; interface supports pinning multiple public keys simultaneously, enabling graceful rotation during security incidents without breaking existing clients.&lt;/p&gt;

&lt;p&gt;This is what enterprise security teams dream about: &lt;strong&gt;mathematical proof that every state transition is authorized.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 3: Developer Experience Without Compromise
&lt;/h2&gt;

&lt;p&gt;"Security is critical. But if the developer experience isn't frictionless, adoption dies in the pull request."&lt;/p&gt;

&lt;p&gt;We heard that. Blueprint wraps its cryptographic engine in a Kotlin DSL so natural, it feels like writing Compose.&lt;/p&gt;

&lt;h3&gt;
  
  
  Server: Build UIs Like You're Writing Jetpack&amp;nbsp;Compose
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Ktor backend - type-safe, auto-completed, cryptographically secure&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;ordersScreen&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;blueprint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"order_list"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"My Orders"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Active orders"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;// Server-owned state that drives dynamic UI&lt;/span&gt;
    &lt;span class="nf"&gt;state&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"total_orders"&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="s"&gt;"5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"username"&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="s"&gt;"John"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;root&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;verticalArrangement&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LayoutArrangement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;START&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;modifiers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nf"&gt;background&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"#F8F9FA"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="nf"&gt;fillMaxSize&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="c1"&gt;// Binds to state["username"] - server changes this instantly&lt;/span&gt;
            &lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;bindString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"username"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TextSize&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;HEADLINE_LARGE&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="nc"&gt;LazyColumn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;contentPadding&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;24f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nc"&gt;Card&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;variant&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CardVariant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;FILLED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;onClickIntentId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"navigate_detail:1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;modifiers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;cornerRadius&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;20f&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="nf"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bottom&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;16f&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="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Order #1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TextSize&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;TITLE_MEDIUM&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="nc"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Refresh"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;variant&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ButtonVariant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;FILLED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;onClickIntentId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"refresh_orders"&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="c1"&gt;// One line to seal it cryptographically&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;signedBlueprint&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CryptoUtils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createSignedBlueprint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ordersScreen&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;currentHash&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No manual hashing. No signature boilerplate. The framework handles the chain automatically.&lt;/p&gt;

&lt;h3&gt;
  
  
  Client: Native Compose Rendering with Built-in Verification
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Composable&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ApplicationStore&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;state&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collectAsState&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;renderer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;remember&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;createDefaultBlueprintRegistry&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nc"&gt;MaterialTheme&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;blueprint&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;LoadingScreen&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

            &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;blueprint&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;blueprint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;intentHandler&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;intent&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;intent&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;The &lt;code&gt;BlueprintComposeRegistry&lt;/code&gt; maps &lt;code&gt;ComponentPayload&lt;/code&gt; directly to Material 3 Compose components. No WebViews. No JavaScript bridges. &lt;strong&gt;100% native rendering, 100% cryptographically verified.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 4: Pure Functional Core - Predictable, Testable, Crash-Free
&lt;/h2&gt;

&lt;p&gt;Blueprint's state machine is built on pure functional programming principles. Every operation returns &lt;code&gt;Either&amp;lt;ChainError, BlueprintChain&amp;gt;&lt;/code&gt; - a monadic type that makes failure a first-class citizen, not an exception.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ApplicationStore.kt - the entire state reduction pipeline&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;handleResolution&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resolution&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Resolution&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;events&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;buildList&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;resolution&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;effects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;effect&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;effect&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nc"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Navigation&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="nc"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;PUSH&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;blueprint&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;let&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ChainEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
                        &lt;span class="nc"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;REPLACE&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;blueprint&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;let&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ChainEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
                        &lt;span class="nc"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;POP&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ChainEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Pop&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resolution&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deltaBlocks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isNotEmpty&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ChainEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ApplyDeltas&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resolution&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deltaBlocks&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="c1"&gt;// Fail-fast monadic fold - any error halts the entire chain update&lt;/span&gt;
    &lt;span class="n"&gt;_state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;currentState&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;foldEither&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;currentState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&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="k"&gt;is&lt;/span&gt; &lt;span class="nc"&gt;Either&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Left&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// Security breach or navigation error - safe, typed, no crash&lt;/span&gt;
                &lt;span class="n"&gt;currentState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Chain integrity violation: ${result.value}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;isLoading&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;
                &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nc"&gt;Either&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Right&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// All transitions validated, chain updated atomically&lt;/span&gt;
                &lt;span class="n"&gt;currentState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chain&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;isLoading&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&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 architecture gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Deterministic behavior&lt;/strong&gt; - same inputs always produce same outputs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trivial unit testing&lt;/strong&gt; - no mocking, just data in, data out.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero runtime crashes from invalid state&lt;/strong&gt; - Either enforces error handling at compile time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mathematical immunity to garbage payloads&lt;/strong&gt; - because the state machine is purely functional and strictly typed, it is mathematically impossible for a malicious payload to force the UI into an undefined, exploitable state. If the payload doesn't fold cleanly into the existing chain, the transition simply doesn't happen.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Part 5: Why This Becomes the&amp;nbsp;Standard
&lt;/h2&gt;

&lt;p&gt;The trajectory is clear:&lt;br&gt;
&lt;strong&gt;2024:&lt;/strong&gt; Regulators in EU and US signal stricter requirements for financial app integrity.&amp;nbsp;&lt;br&gt;
&lt;strong&gt;2025:&lt;/strong&gt; Major banks mandate cryptographic proof of UI authenticity in vendor RFPs.&amp;nbsp;&lt;br&gt;
&lt;strong&gt;2026:&lt;/strong&gt; Zero-Trust SDUI becomes an industry compliance checkbox - like SOC 2 or PCI DSS.&lt;/p&gt;

&lt;p&gt;Blueprint is ready today. It's the only Kotlin Multiplatform framework that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Treats UI as a cryptographically verifiable state machine&amp;nbsp;&lt;/li&gt;
&lt;li&gt;✅ Provides hash chaining to prevent screen injection and replay attacks&amp;nbsp;&lt;/li&gt;
&lt;li&gt;✅ Signs all state mutations with industry-standard RSA&amp;nbsp;&lt;/li&gt;
&lt;li&gt;✅ Delivers native Compose performance on Android, iOS, and Desktop&amp;nbsp;&lt;/li&gt;
&lt;li&gt;✅ Offers a type-safe DSL that eliminates an entire class of serialization bugs&amp;nbsp;&lt;/li&gt;
&lt;li&gt;✅ Aligns with NIST SP 800–207 Zero Trust Architecture principles&amp;nbsp;&lt;/li&gt;
&lt;li&gt;✅ Supports key rotation for enterprise key management policies&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;This is not a niche security feature. It's the missing layer between "dynamic UI" and "auditable UI."&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Security FAQ
&lt;/h2&gt;

&lt;p&gt;Q: &lt;strong&gt;Does this add latency to navigation?&lt;/strong&gt;&lt;br&gt;
A: No. All verification is local. SHA-256 hashing adds &amp;lt; 0.3ms. RSA signature verification adds &amp;lt; 2ms. The entire chain validation completes in under 4ms - less than a single frame at 60 FPS.&lt;/p&gt;

&lt;p&gt;Q: &lt;strong&gt;What if the private key leaks?&lt;/strong&gt;&lt;br&gt;
A: Same as any PKI system - key rotation. Blueprint's &lt;code&gt;SignatureVerifier&lt;/code&gt; interface supports pinning multiple public keys simultaneously. During rotation, the server signs with the new key while clients accept both old and new public keys. Once all clients update, the old key is revoked.&lt;/p&gt;

&lt;p&gt;Q: &lt;strong&gt;Does this work offline?&lt;/strong&gt;&lt;br&gt;
A: The chain validates locally with zero network calls. Navigation between cached screens is fully verified offline. Only new state mutations require fresh server signatures - exactly like JWT validation, but for your UI.&lt;/p&gt;

&lt;p&gt;Q: &lt;strong&gt;Can I use this without the cryptographic features?&lt;/strong&gt;&lt;br&gt;
A: Yes. Set &lt;code&gt;strictMode = false&lt;/code&gt; and the chain operates as a standard navigation stack. You can adopt the SDUI patterns first, then enable cryptographic verification when your compliance requirements demand it.&lt;/p&gt;

&lt;p&gt;Q: &lt;strong&gt;What attack vectors does this actually prevent?&lt;/strong&gt;&lt;br&gt;
A: Blueprint prevents five critical attack classes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Payload injection&lt;/strong&gt; - Malicious screens inserted via compromised CDN or proxy&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;UI replay attacks&lt;/strong&gt; - Old "success" screens replayed to hide errors or fraud&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;State tampering&lt;/strong&gt; - Runtime modification of bound values (account numbers, amounts)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Screen spoofing&lt;/strong&gt; - Phishing screens injected into the navigation flow&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Internal lateral movement&lt;/strong&gt; - Compromised microservice injecting malicious UI payloads&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;Blueprint is open-source under Apache 2.0. The repository includes a fully functional example with a Ktor backend and Compose Desktop client demonstrating the complete cryptographic flow.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/numq/blueprint" rel="noopener noreferrer"&gt;→ GitHub Repository&lt;/a&gt;&lt;br&gt;
&lt;a href="https://github.com/numq/blueprint#readme" rel="noopener noreferrer"&gt;→ Full Documentation &amp;amp; Architecture Guide&lt;/a&gt;&lt;br&gt;
&lt;a href="https://github.com/numq/blueprint/tree/main/example" rel="noopener noreferrer"&gt;→ Example: Order Tracking App with Zero-Trust Chain&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bottom&amp;nbsp;Line
&lt;/h2&gt;

&lt;p&gt;Every time you deserialize a JSON UI payload without cryptographic verification, you're handing a blank check to any attacker who compromises your transport layer. TLS encrypts the pipe. It doesn't prove the payload is authentic.&lt;/p&gt;

&lt;p&gt;Blueprint changes the equation. It transforms your client from a "trusting renderer" into a &lt;strong&gt;verifying validator&lt;/strong&gt; - mathematically certain that every pixel, every button, every state transition originates from your authorized backend.&lt;/p&gt;

&lt;p&gt;Don't take my word for it. Clone the repository, launch the example app, and try to intercept and modify the JSON payload on the fly using Charles Proxy or Proxyman. You'll see Blueprint stop the attack dead in its tracks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don't just ship dynamic UIs. Ship verifiable UIs. Ship Blueprint.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>serverdrivenui</category>
      <category>zerotrust</category>
      <category>kmp</category>
      <category>jetpackcompose</category>
    </item>
    <item>
      <title>Building High-Performance Text Editors in Kotlin Multiplatform with Krope</title>
      <dc:creator>numq</dc:creator>
      <pubDate>Fri, 24 Jul 2026 13:02:16 +0000</pubDate>
      <link>https://dev.to/numq/building-high-performance-text-editors-in-kotlin-multiplatform-with-krope-4o3d</link>
      <guid>https://dev.to/numq/building-high-performance-text-editors-in-kotlin-multiplatform-with-krope-4o3d</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgndssagxbptz1ri8u0qf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgndssagxbptz1ri8u0qf.png" alt="String and rope" width="800" height="437"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you have ever tried building a text editor, an IDE, or a collaborative document tool, you quickly run into a fundamental wall: &lt;strong&gt;how do you store the text?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;At first glance, a standard &lt;code&gt;String&lt;/code&gt; seems fine. But Strings are immutable; changing a single character in a 1MB file requires allocating a brand new 1MB array. So, you move to &lt;code&gt;StringBuilder&lt;/code&gt;. It’s mutable and fast for simple appends, but inserting or deleting text in the middle of a massive document still requires shifting hundreds of thousands of characters in memory — an O(N) operation that causes UI stuttering as your document grows.&lt;/p&gt;

&lt;p&gt;This is where the &lt;strong&gt;Rope&lt;/strong&gt; data structure comes in.&lt;/p&gt;

&lt;p&gt;Today, I want to introduce &lt;a href="https://github.com/numq/krope" rel="noopener noreferrer"&gt;&lt;strong&gt;Krope&lt;/strong&gt;&lt;/a&gt;, a high-performance, memory-safe, and reactive Rope library built entirely in Kotlin Multiplatform. Whether you are targeting JVM, Android, iOS, or WASM, Krope provides the engine you need to build next-generation text editing tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a Rope?
&lt;/h2&gt;

&lt;p&gt;Instead of storing text as a single massive, contiguous array of characters, a &lt;a href="https://en.wikipedia.org/wiki/Rope_(data_structure)" rel="noopener noreferrer"&gt;Rope&lt;/a&gt; stores text as a balanced binary tree (in Krope’s case, utilizing a Red-Black tree variant). The leaves of the tree contain short chunks of strings.&lt;/p&gt;

&lt;p&gt;When you insert text in the middle of a document, a Rope doesn’t copy the whole string. Instead, it splits a leaf node and inserts a new branch. This gives operations like insertion, deletion, and replacement a time complexity of O(log N).&lt;/p&gt;

&lt;p&gt;Furthermore, Krope implements &lt;strong&gt;structural sharing&lt;/strong&gt;. When a modification occurs, Krope doesn’t mutate the existing tree; it returns a &lt;em&gt;new&lt;/em&gt; tree that shares the untouched branches with the old one. This provides safe concurrent access, cheap undo/redo history.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I Built Krope
&lt;/h2&gt;

&lt;p&gt;The idea for Krope was born while building &lt;a href="https://github.com/numq/haskcore" rel="noopener noreferrer"&gt;haskcore&lt;/a&gt; — a full-featured IDE for Haskell written in Compose Desktop. It uses Skia for rendering, Tree-sitter for incremental syntax parsing, and the Language Server Protocol for code intelligence. It’s a real editor with 40+ modules, handling everything from syntax highlighting to code completion and project-wide diagnostics.&lt;/p&gt;

&lt;p&gt;Very quickly, I hit a wall: Kotlin’s standard &lt;code&gt;StringBuilder&lt;/code&gt; couldn't keep up. Editing large Haskell modules meant shifting massive text buffers on every keystroke, causing visible lag during syntax highlighting and LSP synchronization. I needed something better — and the rope data structure, battle-tested in editors like Emacs and Vim since the 90s, was the obvious answer.&lt;/p&gt;

&lt;p&gt;But Kotlin Multiplatform lacked a modern, reactive, and fully-featured rope implementation. So I built Krope to solve the specific pain points I encountered:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Cross-Platform Consistency: haskcore targets the desktop, but the same text engine should work on Android, iOS, and WASM for future ports or companion apps.&lt;/li&gt;
&lt;li&gt;  Reactive by Design: Compose Multiplatform thrives on reactive state. Krope exposes every edit as a &lt;code&gt;SharedFlow&lt;/code&gt; event and every document state as a &lt;code&gt;StateFlow&lt;/code&gt;, making UI integration seamless.&lt;/li&gt;
&lt;li&gt;  Byte-Level Encoding Awareness: LSP servers speak in byte offsets, not character positions. Krope natively tracks UTF-8, UTF-16, and UTF-32, giving you efficient access to byte coordinates backed by an LRU cache.&lt;/li&gt;
&lt;li&gt;  Functional &amp;amp; Safe: Invalid operations shouldn’t crash your editor. Krope uses an &lt;code&gt;Either&amp;lt;Throwable, Result&amp;gt;&lt;/code&gt; pattern, so out-of-bounds inserts return errors you can handle gracefully — critical when processing remote edits from LSP or collaborative peers.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Inside the Krope API
&lt;/h2&gt;

&lt;p&gt;Krope separates the underlying data structure from the editor API. The primary interface you interact with is the &lt;code&gt;TextBuffer&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here is how simple it is to get started:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;io.github.numq.krope.core.Encoding&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;io.github.numq.krope.text.*&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;kotlinx.coroutines.launch&lt;/span&gt;

&lt;span class="c1"&gt;// 1. Initialize the buffer&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;factory&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;RopeTextBufferFactory&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;buffer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Hello World"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;encoding&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Encoding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;UTF8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;lineEnding&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TextLineEnding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;LF&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;enablePooling&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt; &lt;span class="c1"&gt;// Enables internal string interning to save memory&lt;/span&gt;
&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fold&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;onLeft&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;onRight&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;it&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;h2&gt;
  
  
  The Reactive Paradigm
&lt;/h2&gt;

&lt;p&gt;In a standard text editor, the UI needs to know exactly &lt;em&gt;what&lt;/em&gt; changed to update the screen or send a diff over the network for collaborative editing. Krope exposes two flows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;snapshot: StateFlow&amp;lt;TextSnapshot&amp;gt;&lt;/code&gt;: Represents the immutable current state of the document.&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;data: SharedFlow&amp;lt;TextEdit.Data&amp;gt;&lt;/code&gt;: Emits granular events whenever the text changes.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Observe all changes in real-time&lt;/span&gt;
&lt;span class="nf"&gt;launch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collect&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;editData&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;editData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nc"&gt;TextEdit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Single&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Insert&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; 
                &lt;span class="nf"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Inserted '${editData.insertedText}' at ${editData.startPosition}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nc"&gt;TextEdit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Single&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Delete&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; 
                &lt;span class="nf"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Deleted text ending at ${editData.oldEndPosition}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nc"&gt;TextEdit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Single&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Replace&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; 
                &lt;span class="nf"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Replaced '${editData.oldText}' with '${editData.newText}'"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nc"&gt;TextEdit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Batch&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; 
                &lt;span class="nf"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Batch processed ${editData.singles.size} operations atomically"&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;h2&gt;
  
  
  Line and Column Awareness
&lt;/h2&gt;

&lt;p&gt;Most ropes just deal with raw character offsets. Krope natively understands &lt;code&gt;Lines&lt;/code&gt; and &lt;code&gt;Columns&lt;/code&gt; through &lt;code&gt;TextPosition&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// "Hello Beautiful World"&lt;/span&gt;
&lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;TextPosition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;column&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s"&gt;" Beautiful"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Querying the snapshot is instantaneous thanks to cached tree node metadata&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;snapshot&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;
&lt;span class="nf"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Total Lines: ${snapshot.lines}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Max Line Length: ${snapshot.maxLineLength}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// Get byte offset for an LSP (Language Server Protocol)&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;byteOffset&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getBytePosition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;TextPosition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;column&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Atomic Batch Operations
&lt;/h2&gt;

&lt;p&gt;When you refactor code (like renaming a variable that appears 10 times in a file), you don’t want to trigger 10 separate UI redraws or clutter the undo stack. Krope supports &lt;code&gt;withBatch&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;withBatch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;batch&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;TextRange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;TextPosition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;39&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nc"&gt;TextPosition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="s"&gt;"vatAmount"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;TextRange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;TextPosition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;19&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nc"&gt;TextPosition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="s"&gt;"vatAmount"&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 applies all edits internally, rebalances the tree &lt;em&gt;once&lt;/em&gt;, and emits a single &lt;code&gt;TextEdit.Data.Batch&lt;/code&gt; event to your listeners.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance: The 100 Megabyte&amp;nbsp;Test
&lt;/h2&gt;

&lt;p&gt;To answer the age-old question - &lt;em&gt;"How does it perform with very large files?"&lt;/em&gt; - I ran JMH benchmarks on the JVM comparing Krope to &lt;code&gt;StringBuilder&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We simulated the "Real Editor Scenario": &lt;strong&gt;100 consecutive edits&lt;/strong&gt; (insertions) occurring right in the middle of a document. We scaled the document size from 100,000 characters all the way to &lt;strong&gt;100,000,000 characters (approximately 200 MB of text)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Here are the results (in milliseconds per 100 operations):&lt;/p&gt;

&lt;p&gt;100,000 symbols&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Krope: &lt;strong&gt;~0.05 ms&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;StringBuilder: &lt;strong&gt;~0.16 ms&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Krope Advantage: &lt;strong&gt;~3x faster&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;1,000,000 symbols&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Krope: &lt;strong&gt;~0.19 ms&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;StringBuilder: &lt;strong&gt;~1.74 ms&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Krope Advantage: &lt;strong&gt;~9x faster&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;10,000,000 symbols&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Krope: &lt;strong&gt;~2.07 ms&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;StringBuilder: &lt;strong&gt;~17.86 ms&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Krope Advantage: &lt;strong&gt;~8.5x faster&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;100,000,000 symbols&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Krope: &lt;strong&gt;~19.32 ms&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;StringBuilder: &lt;strong&gt;~186.41 ms&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Krope Advantage: &lt;strong&gt;~9.6x faster&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why does StringBuilder choke?
&lt;/h2&gt;

&lt;p&gt;Look at how the &lt;code&gt;StringBuilder&lt;/code&gt; time multiplies by 10 every time the file size multiplies by 10. This is the curse of O(N) complexity. When you insert a single character in the middle of a 200MB &lt;code&gt;StringBuilder&lt;/code&gt;, Java has to use &lt;code&gt;System.arraycopy&lt;/code&gt; to physically shift 100MB of data in RAM to make room. Doing this 100 times forces the CPU to move &lt;strong&gt;10 gigabytes&lt;/strong&gt; of memory. This is exactly what causes UI thread stuttering and input lag in text editors.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why does Krope&amp;nbsp;win?
&lt;/h2&gt;

&lt;p&gt;Because of &lt;strong&gt;structural sharing&lt;/strong&gt; and its Red-Black tree foundation. When making 100 edits, Krope doesn't copy 100MB of arrays. It simply splits a small leaf node, updates a few pointers up to the root, and does a lightning-fast O(log N) tree rebalance.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(Note: In the benchmark above, the ~19.32 ms for Krope actually included the time to parse the 200MB string into a Rope tree from scratch on every iteration! The edits themselves are nearly instantaneous).&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In a real-world typing or refactoring scenario, Krope maintains incredibly flat, predictable latency regardless of whether your file is 10 Kilobytes or 200 Megabytes, keeping your UI perfectly smooth while drastically reducing heap allocations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get Involved
&lt;/h2&gt;

&lt;p&gt;If you are building text-heavy applications in Compose Multiplatform, writing a code editor, or experimenting with CRDTs for collaborative editing, give Krope a try.&lt;/p&gt;

&lt;p&gt;The architecture is highly modular, separating the core data structures from the TextBuffer API, and includes built-in support for &lt;code&gt;kotlinx.serialization&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Check out the source code, play with the examples, and star the repository on GitHub!&lt;/p&gt;

&lt;p&gt;🔗 &lt;a href="https://github.com/numq/krope" rel="noopener noreferrer"&gt;&lt;strong&gt;GitHub Repository: Krope&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>datastructures</category>
      <category>kotlin</category>
      <category>kmp</category>
      <category>performance</category>
    </item>
    <item>
      <title>JetBrains Just Changed KMP Structure. Here's What They Didn't Tell You.</title>
      <dc:creator>numq</dc:creator>
      <pubDate>Mon, 18 May 2026 18:18:29 +0000</pubDate>
      <link>https://dev.to/numq/jetbrains-just-changed-kmp-structure-heres-what-they-didnt-tell-you-6kb</link>
      <guid>https://dev.to/numq/jetbrains-just-changed-kmp-structure-heres-what-they-didnt-tell-you-6kb</guid>
      <description>&lt;p&gt;JetBrains &lt;a href="https://blog.jetbrains.com/kotlin/2026/05/new-kmp-default-structure/" rel="noopener noreferrer"&gt;announced&lt;/a&gt; a new default project structure for KMP. Two shared modules — &lt;code&gt;sharedLogic&lt;/code&gt; and &lt;code&gt;sharedUI&lt;/code&gt; — plus separate application modules for each platform. Clean. Simple. Obvious.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffoznnzfpoowhb26d0u2i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffoznnzfpoowhb26d0u2i.png" alt="New KMP template" width="760" height="1202"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's also a trap.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem with &lt;code&gt;sharedLogic&lt;/code&gt; and &lt;code&gt;sharedUI&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;A fresh &lt;code&gt;sharedLogic&lt;/code&gt; module looks innocent. Business logic, models, repositories. &lt;code&gt;sharedUI&lt;/code&gt; holds your Composables. What could go wrong?&lt;/p&gt;

&lt;p&gt;Three months later &lt;code&gt;sharedLogic&lt;/code&gt; contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Business logic&lt;/li&gt;
&lt;li&gt;Network clients&lt;/li&gt;
&lt;li&gt;Database models&lt;/li&gt;
&lt;li&gt;Constants&lt;/li&gt;
&lt;li&gt;Utility functions&lt;/li&gt;
&lt;li&gt;Something called &lt;code&gt;CommonUtils.kt&lt;/code&gt; that nobody understands but everyone is afraid to delete&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;sharedLogic&lt;/code&gt; has no internal boundaries. Everything depends on everything. You can import a database model from a use case and nobody stops you. The compiler certainly won't.&lt;/p&gt;

&lt;p&gt;JetBrains tells you where to start. They don't tell you what happens when two modules become 50,000 lines.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually happens
&lt;/h2&gt;

&lt;p&gt;You try to split &lt;code&gt;sharedLogic&lt;/code&gt; into more modules. But there are no rules. So you end up with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;:sharedLogic:core&lt;/code&gt; — the new dumping ground&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;:sharedLogic:data&lt;/code&gt; — which also contains business logic somehow&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The dependencies look like spaghetti. Changing one module breaks three others. You didn't enforce boundaries early, and now every refactoring is a gamble.&lt;/p&gt;

&lt;h2&gt;
  
  
  FLIP: the next step after &lt;code&gt;sharedLogic&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/numq/flip" rel="noopener noreferrer"&gt;FLIP&lt;/a&gt; is an architectural pattern that gives you rules for splitting &lt;code&gt;sharedLogic&lt;/code&gt; before it becomes a problem.&lt;/p&gt;

&lt;p&gt;Five layers. One direction. Zero horizontal dependencies.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;th&gt;Depends on&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;:common:core&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Foundation&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;:service&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Domain logic without UI&lt;/td&gt;
&lt;td&gt;&lt;code&gt;:common:core&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;:feature&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;UI features&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;:service&lt;/code&gt;, &lt;code&gt;:common:core&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;:entrypoint&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;DI assembly + slot composition&lt;/td&gt;
&lt;td&gt;Everything above&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;:platform&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Platform entry point&lt;/td&gt;
&lt;td&gt;&lt;code&gt;:entrypoint&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The rule is simple: &lt;strong&gt;features don't know about other features. services don't know about other services.&lt;/strong&gt; Dependencies only flow down.&lt;/p&gt;

&lt;p&gt;Your IDE enforces this. You literally cannot import a feature from another feature — Gradle won't compile. The structure prevents the dumpster fire before it starts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with JetBrains. Grow with FLIP.
&lt;/h2&gt;

&lt;p&gt;The new KMP structure is a great starting point. Two modules is better than one. But when &lt;code&gt;sharedLogic&lt;/code&gt; starts feeling like a junk drawer, you know what to do.&lt;/p&gt;

&lt;p&gt;FLIP is at &lt;a href="https://github.com/numq/flip" rel="noopener noreferrer"&gt;github.com/numq/flip&lt;/a&gt;. Template included. Konsist tests included. Rules over conventions.&lt;/p&gt;

</description>
      <category>kotlin</category>
      <category>kmp</category>
      <category>architecture</category>
      <category>jetpackcompose</category>
    </item>
    <item>
      <title>Stop Fighting Your State. Reduce And Conquer It.</title>
      <dc:creator>numq</dc:creator>
      <pubDate>Wed, 13 May 2026 18:16:46 +0000</pubDate>
      <link>https://dev.to/numq/stop-fighting-your-state-reduce-and-conquer-it-1ijf</link>
      <guid>https://dev.to/numq/stop-fighting-your-state-reduce-and-conquer-it-1ijf</guid>
      <description>&lt;p&gt;&lt;strong&gt;MVVM&lt;/strong&gt; seemed simple. Then you added ten MutableStateFlow properties to your ViewModel. &lt;strong&gt;MVI&lt;/strong&gt; promised purity. Then you wrote a middleware for side effects.&lt;/p&gt;

&lt;p&gt;There’s a better way.&lt;/p&gt;

&lt;h1&gt;
  
  
  The Problem With MVVM
&lt;/h1&gt;

&lt;p&gt;A typical ViewModel looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProfileViewModel&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ViewModel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MutableStateFlow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;email&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MutableStateFlow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;isLoading&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MutableStateFlow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;error&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MutableStateFlow&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?&amp;gt;(&lt;/span&gt;&lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;updateProfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;viewModelScope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;profileService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="nd"&gt;@ProfileViewModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&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;Five mutable properties. Loading state scattered across three places. Error handling duplicated in every method. And the ViewModel doesn’t own its side effects — &lt;code&gt;viewModelScope&lt;/code&gt; does.&lt;/p&gt;

&lt;h1&gt;
  
  
  The Problem With MVI
&lt;/h1&gt;

&lt;p&gt;MVI fixes the state explosion by putting everything in a sealed interface:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;ProfileState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="kd"&gt;object&lt;/span&gt; &lt;span class="nc"&gt;Loading&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ProfileState&lt;/span&gt;
    &lt;span class="kd"&gt;data class&lt;/span&gt; &lt;span class="nc"&gt;Loaded&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ProfileState&lt;/span&gt;
    &lt;span class="kd"&gt;data class&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ProfileState&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But MVI doesn’t tell you how to handle side effects. Some use middleware. Some use &lt;code&gt;Channel&lt;/code&gt;. Some hack it with &lt;code&gt;LaunchedEffect&lt;/code&gt;. Every project reinvents the wheel.&lt;/p&gt;

&lt;h1&gt;
  
  
  Reduce &amp;amp;&amp;nbsp;Conquer
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp475o28oss398wyz4qoh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp475o28oss398wyz4qoh.png" alt="Reduce &amp;amp; Conquer layers" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The core idea: &lt;em&gt;a reducer is a pure function that returns more than just state&lt;/em&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;data class&lt;/span&gt; &lt;span class="nc"&gt;Transition&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;State&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;State&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;events&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;emptyList&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;effects&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;emptyList&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;A transition has three outputs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;State&lt;/strong&gt; - the new state&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Events&lt;/strong&gt; - one-shot notifications (navigation, snackbar)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Effects&lt;/strong&gt; - long-running or async work&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Effects are first-class citizens:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;Effect&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;data class&lt;/span&gt; &lt;span class="nc"&gt;Stream&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;flow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Flow&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;,&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;strategy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Strategy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Strategy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Sequential&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;fallback&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;suspend&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Throwable&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;)?&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Effect&lt;/span&gt;

    &lt;span class="kd"&gt;data class&lt;/span&gt; &lt;span class="nc"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;fallback&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;suspend&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Throwable&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;)?&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;block&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;suspend&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Command&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Effect&lt;/span&gt;

    &lt;span class="kd"&gt;data class&lt;/span&gt; &lt;span class="nc"&gt;Cancel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Effect&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Stream&lt;/strong&gt; - subscribes to a flow, emits commands back&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Action&lt;/strong&gt; - runs one async operation, emits a command&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cancel&lt;/strong&gt; - cancels by key, preventing leaks&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  A Reducer in&amp;nbsp;Practice
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProfileReducer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;profileService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ProfileService&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Reducer&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ProfileState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;ProfileCommand&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;ProfileEvent&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ProfileState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ProfileCommand&lt;/span&gt;
    &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;Transition&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ProfileState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;ProfileEvent&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nc"&gt;ProfileCommand&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;UpdateProfile&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isLoading&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;effect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nf"&gt;action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"update_profile"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;fallback&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;ProfileCommand&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ProfileError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="n"&gt;block&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;profileService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="nc"&gt;ProfileCommand&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ProfileUpdated&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="k"&gt;is&lt;/span&gt; &lt;span class="nc"&gt;ProfileCommand&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ProfileUpdated&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isLoading&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ProfileEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;NavigateBack&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nc"&gt;ProfileCommand&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ProfileError&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;isLoading&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;throwable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&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;No &lt;code&gt;viewModelScope&lt;/code&gt;. No &lt;code&gt;LaunchedEffect&lt;/code&gt;. No mutable properties. One pure function.&lt;/p&gt;

&lt;h1&gt;
  
  
  Why This Is The Benchmark
&lt;/h1&gt;

&lt;h2&gt;
  
  
  MVVM
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;State&lt;/strong&gt;: Multiple MutableStateFlow&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Side effects&lt;/strong&gt;: viewModelScope.launch&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cancellation&lt;/strong&gt;: Manual&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Testing&lt;/strong&gt;: Mock ViewModel&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  MVI
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;State&lt;/strong&gt;: Single sealed class/interface&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Side effects&lt;/strong&gt;: Ad-hoc (middleware, Channel)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cancellation&lt;/strong&gt;: Manual&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Testing&lt;/strong&gt;: Mock reducer + middleware&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Reduce &amp;amp;&amp;nbsp;Conquer
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;State&lt;/strong&gt;: Single sealed interface&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Side effects&lt;/strong&gt;: Built-in (Effect)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cancellation&lt;/strong&gt;: Automatic by key&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Testing&lt;/strong&gt;: One pure function call&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Testing a reducer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Test&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;`update&lt;/span&gt; &lt;span class="n"&gt;profile&lt;/span&gt; &lt;span class="n"&gt;sets&lt;/span&gt; &lt;span class="n"&gt;loading&lt;/span&gt; &lt;span class="n"&gt;and&lt;/span&gt; &lt;span class="n"&gt;fires&lt;/span&gt; &lt;span class="nf"&gt;effect`&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;transition&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;reducer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ProfileState&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ProfileCommand&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;UpdateProfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Alice"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;assertTrue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;assertEquals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;effects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;assertEquals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&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;One call. No coroutines. No mocks.&lt;/p&gt;

&lt;h1&gt;
  
  
  The Rule
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;State flows down. Commands flow up. Effects manage the rest.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The View sends Commands. The Reducer returns a Transition with new State, Events, and Effects. The Feature executes Effects and feeds resulting Commands back into the Reducer. That's it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqikpke5i4lst0d55qcil.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqikpke5i4lst0d55qcil.png" alt="Reduce &amp;amp; Conquer diagram" width="577" height="58"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Full implementation in the &lt;a href="https://www.github.numq/reduce-and-conquer" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>kotlin</category>
      <category>mvi</category>
      <category>mvvm</category>
      <category>jetpackcompose</category>
    </item>
    <item>
      <title>A recipe for the Perfect Use Case</title>
      <dc:creator>numq</dc:creator>
      <pubDate>Wed, 13 May 2026 18:05:16 +0000</pubDate>
      <link>https://dev.to/numq/a-recipe-for-the-perfect-use-case-5201</link>
      <guid>https://dev.to/numq/a-recipe-for-the-perfect-use-case-5201</guid>
      <description>&lt;p&gt;Today we’re cooking the perfect Use Case — and &lt;strong&gt;CQRS&lt;/strong&gt; will be our recipe.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ingredients
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Action&lt;/strong&gt; — fire and forget (logout, clear cache)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Query&lt;/strong&gt; — read data (list products)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Command&lt;/strong&gt; — write data (update profile)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exchange&lt;/strong&gt; — transform data (login)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All four types extend a sealed interface:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;UseCase&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Output&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;Action&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;UseCase&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Unit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Unit&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

    &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Output&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;UseCase&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Unit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Output&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

    &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Input&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;UseCase&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Unit&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

    &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;Exchange&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Output&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;UseCase&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Output&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffih8nhewo7n1cn77e0ze.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffih8nhewo7n1cn77e0ze.png" alt="Usecases" width="497" height="298"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Three Ways to Season It
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Arrow (Typed Errors)&lt;/strong&gt; — the chef’s choice:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GenerateSeed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;seedService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;SeedService&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;UseCase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Action&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;suspend&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;Raise&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Throwable&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;.&lt;/span&gt;&lt;span class="nf"&gt;action&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
        &lt;span class="n"&gt;seedService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generateSeed&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;bind&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;&lt;strong&gt;Result (Standard Wrapper)&lt;/strong&gt; — zero dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GenerateSeed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;service&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;SeedService&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;UseCase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Action&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;suspend&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;action&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generateSeed&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;getOrThrow&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;&lt;strong&gt;Raw (Direct Execution)&lt;/strong&gt; — zero overhead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GenerateSeed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;service&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;SeedService&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;UseCase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Action&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;suspend&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;action&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generateSeed&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;h2&gt;
  
  
  Why This Recipe Works
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;No more &lt;code&gt;UseCase&amp;lt;Unit, Unit&amp;gt;&lt;/code&gt; noise&lt;/li&gt;
&lt;li&gt;Every use case follows the same structure&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Query&lt;/code&gt; or &lt;code&gt;Command&lt;/code&gt; makes intent obvious&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Full implementation in the &lt;a href="https://www.github.com/numq/usecase" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>kotlin</category>
      <category>architecture</category>
      <category>cqrs</category>
      <category>cleancode</category>
    </item>
    <item>
      <title>FLIP: Modular Architecture for KMP</title>
      <dc:creator>numq</dc:creator>
      <pubDate>Wed, 13 May 2026 17:53:17 +0000</pubDate>
      <link>https://dev.to/numq/flip-modular-architecture-for-kmp-89c</link>
      <guid>https://dev.to/numq/flip-modular-architecture-for-kmp-89c</guid>
      <description>&lt;p&gt;While building &lt;strong&gt;&lt;a href="https://www.github.com/numq/haskcore" rel="noopener noreferrer"&gt;haskcore&lt;/a&gt;&lt;/strong&gt; - an IDE for Haskell on Compose Desktop - I extracted rules that allowed me to create an architecture of 40 isolated modules, where each module knows only what it needs.&lt;/p&gt;

&lt;p&gt;That's how &lt;strong&gt;&lt;a href="https://www.github.com/numq/flip" rel="noopener noreferrer"&gt;FLIP - Feature-Layered Isolated Platform&lt;/a&gt;&lt;/strong&gt; - was born.&lt;/p&gt;

&lt;p&gt;The main problem I faced during development: &lt;strong&gt;horizontal dependencies make the system fragile&lt;/strong&gt;. The solution - strict vertical hierarchy.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff8vwgtyjyk0ftrm1z5m1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff8vwgtyjyk0ftrm1z5m1.png" alt="FLIP layers" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;FLIP&lt;/strong&gt; consists of the following isolated modules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;:common:core&lt;/code&gt; - common logic components.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;:common:presentation&lt;/code&gt; - common UI components.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;:service&lt;/code&gt; - UI-less domain module. Provides a service interface.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;SeedService&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;AutoCloseable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Flow&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Seed&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

    &lt;span class="k"&gt;suspend&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;generateSeed&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;Either&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Throwable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Unit&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;:feature:core&lt;/code&gt; — logic module of a UI-feature. Knows nothing about other features. Uses services from &lt;code&gt;:service&lt;/code&gt;. Provides use cases.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GenerateRemoteRandomSeed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;seedService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;SeedService&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;UseCase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Action&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;suspend&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;Raise&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Throwable&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;.&lt;/span&gt;&lt;span class="nf"&gt;action&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;seedService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generateSeed&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;bind&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;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;:feature:presentation&lt;/code&gt; — UI module of a UI-feature. Knows only about its &lt;code&gt;:feature:core&lt;/code&gt; and uses its use cases. Provides the view. Knows only about its own &lt;code&gt;:feature:core&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;:entrypoint&lt;/code&gt; — module where dependency configuration and composition of views from &lt;code&gt;:feature:presentation&lt;/code&gt; into a single Composable happens.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;:platform&lt;/code&gt; — module that initializes the Composable provided by &lt;code&gt;:entrypoint&lt;/code&gt; for each platform.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwfti4lrghvsyyaccc007.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwfti4lrghvsyyaccc007.png" alt="FLIP dependencies" width="617" height="544"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Arrows show the direction of dependencies.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How to structure your system?
&lt;/h2&gt;

&lt;p&gt;Decide which features your app should have, then determine whether each feature has a UI:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If it does — it’s a &lt;strong&gt;feature&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;If it doesn’t — it’s a &lt;strong&gt;service&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;If one feature’s logic is needed by another — extract it into a separate &lt;strong&gt;service&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Important: both &lt;code&gt;:service&lt;/code&gt; and &lt;code&gt;:feature:core&lt;/code&gt; contain domain logic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;:service&lt;/code&gt; — core domain.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;:feature:core&lt;/code&gt; — presentation domain (use cases).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Wrong:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;:service:foo&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;:feature:foo:core&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;:feature:foo:presentation&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Right:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;:service:foo&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;:feature:bar:core&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;:feature:bar:presentation&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Navigation in &lt;strong&gt;FLIP&lt;/strong&gt; is just another feature. It aggregates and routes views passed as lambda slots, keeping it independent from other features.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nc"&gt;NavigationView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;splash&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;SplashView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;applicationScope&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;applicationScope&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;profile&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;ProfileView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;applicationScope&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;applicationScope&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 approach lets you add new screens without touching existing features.&lt;/p&gt;

&lt;p&gt;A complete example is available in the &lt;a href="https://www.github.com/numq/flip" rel="noopener noreferrer"&gt;official GitHub repository&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>kotlin</category>
      <category>kmp</category>
      <category>architecture</category>
      <category>jetpackcompose</category>
    </item>
  </channel>
</rss>
