<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://awesome.red-badger.com//feed.xml" rel="self" type="application/atom+xml" /><link href="https://awesome.red-badger.com//" rel="alternate" type="text/html" /><updated>2026-03-10T16:58:13+00:00</updated><id>https://awesome.red-badger.com//feed.xml</id><title type="html">Awesome Badger</title><subtitle>Assorted thinking (mostly) about technology written by the Badgers.</subtitle><author><name>Red Badger</name></author><entry><title type="html">WASM: beyond the browser and back again</title><link href="https://awesome.red-badger.com//pataruco/wasm-beyond-the-browser-and-back-again" rel="alternate" type="text/html" title="WASM: beyond the browser and back again" /><published>2026-03-09T12:00:00+00:00</published><updated>2026-03-09T12:00:00+00:00</updated><id>https://awesome.red-badger.com//pataruco/wasm-beyond-the-browser-and-back-again</id><content type="html" xml:base="https://awesome.red-badger.com//pataruco/wasm-beyond-the-browser-and-back-again"><![CDATA[<h2 id="what-is-webassembly">What is WebAssembly?</h2>

<p>WebAssembly (WASM) is a binary instruction format designed as a portable compilation target for programming languages. Think of it as a compact, fast, sandboxed virtual machine that runs alongside JavaScript in the browser — and increasingly, outside it too.</p>

<p>The key properties that make WASM interesting:</p>

<ul>
  <li>Fast: near-native execution speed, with predictable performance</li>
  <li>Safe: runs in a sandboxed memory space, isolated from the host</li>
  <li>Portable: the same <code class="language-plaintext highlighter-rouge">.wasm</code> binary runs on any platform with a WASM runtime</li>
  <li>Language-agnostic: you can compile Rust, C, C++, Go, and many other languages to WASM</li>
</ul>

<pre><code class="language-mermaid">graph LR
    A[Rust / C / Go] --&gt;|compile| B[.wasm binary]
    B --&gt; C[Browser]
    B --&gt; D[Zed editor]
    B --&gt; E[Node.js]
    B --&gt; F[Cloudflare Workers]
    style B fill:#f9d71c,stroke:#333,color:#000
</code></pre>

<p>What makes WASM particularly compelling is the safety guarantee: since it runs in a sandbox, a host application can load and execute third-party WASM modules without worrying about them accessing the file system, network, or memory outside their allocated space. This is exactly why both browser extensions and code editors have adopted it as their extension format.</p>

<h2 id="how-i-first-heard-about-it">How I first heard about it</h2>

<p>I first came across WebAssembly through Stu, a colleague, who shared a message about it on Slack:</p>

<blockquote>
  <p>On December 5, The World Wide Web Consortium (W3C) announced that the WebAssembly Core Specification is now an official web standard. This makes WebAssembly the fourth language for the web, following HTML, CSS, and JavaScript.</p>
</blockquote>

<p>After sending this message on 26th December 2019, it planted a seed, but it felt like something reserved for game engines and image processing libraries — not the sort of thing I would use day to day.</p>

<p>What followed were three separate encounters with WASM, each teaching me something different. First, a machine learning model running entirely in the browser. Then, a Firefox extension where Rust makes decisions and TypeScript executes them. Finally, a Zed editor plugin where WASM is just a thin bootstrap layer. Together, they showed me that WASM is not one thing — it is a spectrum of integration patterns.</p>

<h2 id="my-first-encounter-a-small-language-model">My first encounter: a small language model</h2>

<p>My first real experience using WASM happened in an unexpected way. I was working on a project where we needed the browser to recognise images as part of a client’s Know Your Customer (KYC) process. We used a third-party service, and my job was to integrate our system with their API.</p>

<p>The third-party SDK shipped a machine learning model compiled to WASM. This model ran entirely in the browser — it could analyse an image of a passport or driving licence and extract the relevant fields, all without sending the image to a server. The WASM module handled the heavy computation (running the ML inference), while JavaScript orchestrated the camera feed and UI.</p>

<p>It was eye-opening to see that a machine learning model could run entirely on the user’s computer without communicating with a server.</p>

<p>This taught me something important about WASM: it’s not just a faster version of JavaScript. WASM allows you to use powerful tools and libraries from other languages, such as Rust, C, and Python, in places that previously supported only JavaScript. This led me to choose Rust and WASM when I needed more advanced features for my browser extension.</p>

<h2 id="stained-wall-a-firefox-extension-i-built-powered-by-wasm">Stained Wall: a Firefox extension I built, powered by WASM</h2>

<p>As you most likely know, I am Venezuelan and access to the news is often blocked by websites or not reachable from my country. Since I fled, I normally screenshot news articles and share them with my people who are still there so they can stay informed.</p>

<p>So, some of the news outlets I follow have gone to a paid subscription model, and I can’t access them from my country either. That’s why I built Stained Wall — a Firefox extension that helps me with that using WASM.</p>

<h3 id="the-architecture">The architecture</h3>

<p>It is a Firefox Manifest V3 (MV3) browser extension. Its architecture follows a clear principle: Rust decides, TypeScript executes.</p>

<p>The Rust/WASM engine handles all the logic — matching URLs against site configurations, generating bypass plans, and deciding which scripts to block. TypeScript handles everything that requires browser APIs — DOM manipulation, request interception, and messaging between the background service worker and content scripts.</p>

<pre><code class="language-mermaid">flowchart TB
    subgraph "Background Service Worker"
        A[Page navigation event] --&gt; B[WASM Engine.resolve url]
        B --&gt; C{Plan found?}
        C --&gt;|Yes| D[Send plan to content script]
        C --&gt;|No| E[Set inactive icon]
        D --&gt; F[Block scripts via webRequest]
        D --&gt; G[Spoof User-Agent if needed]
    end

    subgraph "Content Script"
        H[Receive plan] --&gt; I[Phase 1: Hide elements CSS]
        I --&gt; J[Phase 2: Remove DOM elements]
        J --&gt; K[Phase 3: Fetch archive / extract JSON-LD]
    end

    D --&gt; H

    style B fill:#f9d71c,stroke:#333,color:#000
</code></pre>

<h3 id="why-wasm-for-a-browser-extension">Why WASM for a browser extension?</h3>

<p>You might ask: why not just write the matching logic in TypeScript? Three reasons:</p>

<ol>
  <li>
    <p>Safety: Rust’s type system and borrow checker catch entire categories of bugs at compile time. The engine forbids <code class="language-plaintext highlighter-rouge">unsafe</code> code entirely (<code class="language-plaintext highlighter-rouge">#![forbid(unsafe_code)]</code>), so there are zero memory safety concerns in the WASM module.</p>
  </li>
  <li>
    <p>Testability: The Rust engine can be tested natively with <code class="language-plaintext highlighter-rouge">cargo test</code>, without needing a browser environment. Unit tests for URL matching, plan generation, and script blocking run in milliseconds.</p>
  </li>
  <li>
    <p>Separation of concerns: The engine knows nothing about browser APIs. It takes a URL string and returns a JSON plan. This makes it trivially easy to reason about: given this input, what output do I get?</p>
  </li>
</ol>

<h3 id="features">Features</h3>

<p>The extension supports a rich set of bypass strategies, all declared in a <code class="language-plaintext highlighter-rouge">sites.json</code> configuration file:</p>

<table>
  <thead>
    <tr>
      <th>Strategy</th>
      <th>What it does</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">hide</code></td>
      <td>Injects CSS to hide paywall overlays immediately, before the page paints</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">remove</code></td>
      <td>Deletes DOM elements after the page loads</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">block_scripts</code></td>
      <td>Intercepts and cancels network requests matching regex patterns</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">spoof_useragent</code></td>
      <td>Replaces the User-Agent header (e.g. to impersonate Googlebot)</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">archive</code></td>
      <td>Fetches the article from an archive service and injects the content</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">json_ld</code></td>
      <td>Extracts article text from embedded JSON-LD structured data</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">inject_css</code></td>
      <td>Injects custom CSS rules to override paywall styling</td>
    </tr>
  </tbody>
</table>

<p>Each strategy is declarative — you configure it in JSON, and the Rust engine generates the appropriate plan:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"A fictitious site"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"domains"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"something.com"</span><span class="p">],</span><span class="w">
  </span><span class="nl">"strategy"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"archive"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"paywall_selector"</span><span class="p">:</span><span class="w"> </span><span class="s2">"teg-page-wall"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"article_selector"</span><span class="p">:</span><span class="w"> </span><span class="s2">"main"</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="nl">"hide"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"div[class*=</span><span class="se">\"</span><span class="s2">adComponent</span><span class="se">\"</span><span class="s2">]"</span><span class="p">,</span><span class="w"> </span><span class="s2">".wall-overlay"</span><span class="p">]</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<h3 id="the-browser-extension-api">The browser extension API</h3>

<p>Working with the Firefox extension API in MV3 requires careful choreography. The WASM module must be loaded in the background service worker, but MV3’s Content Security Policy restricts how you can do this. The key pieces:</p>

<ol>
  <li>Declare WASM as a web-accessible resource (<code class="language-plaintext highlighter-rouge">manifest.json</code>):</li>
</ol>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"web_accessible_resources"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="p">{</span><span class="w">
      </span><span class="nl">"resources"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"pkg/stained_wall_engine_bg.wasm"</span><span class="p">],</span><span class="w">
      </span><span class="nl">"matches"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"&lt;all_urls&gt;"</span><span class="p">]</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<ol>
  <li>Allow WASM evaluation in the CSP:</li>
</ol>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"content_security_policy"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"extension_pages"</span><span class="p">:</span><span class="w"> </span><span class="s2">"script-src 'self' 'wasm-unsafe-eval'"</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<ol>
  <li>Load the WASM module using <code class="language-plaintext highlighter-rouge">browser.runtime.getURL()</code>:</li>
</ol>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">wasmUrl</span> <span class="o">=</span> <span class="nx">browser</span><span class="p">.</span><span class="nx">runtime</span><span class="p">.</span><span class="nf">getURL</span><span class="p">(</span><span class="dl">'</span><span class="s1">pkg/stained_wall_engine_bg.wasm</span><span class="dl">'</span><span class="p">);</span>
<span class="k">await</span> <span class="nf">init</span><span class="p">({</span> <span class="na">module_or_path</span><span class="p">:</span> <span class="nx">wasmUrl</span> <span class="p">});</span>
<span class="kd">const</span> <span class="nx">sitesJson</span> <span class="o">=</span> <span class="k">await</span> <span class="nf">fetch</span><span class="p">(</span><span class="nx">browser</span><span class="p">.</span><span class="nx">runtime</span><span class="p">.</span><span class="nf">getURL</span><span class="p">(</span><span class="dl">'</span><span class="s1">sites.json</span><span class="dl">'</span><span class="p">));</span>
<span class="nx">engine</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Engine</span><span class="p">(</span><span class="k">await</span> <span class="nx">sitesJson</span><span class="p">.</span><span class="nf">text</span><span class="p">());</span>
</code></pre></div></div>

<p>One subtle challenge is timing. The content script might load before the WASM engine has finished initialising in the background script. To handle this, Stained Wall uses a dual messaging pattern:</p>

<ul>
  <li>Push: the background script sends the plan to the content script as soon as the engine resolves it</li>
  <li>Pull: the content script also requests the plan from the background script as a fallback</li>
</ul>

<p>This ensures the plan reaches the content script regardless of initialisation order.</p>

<h3 id="safe-dom-operations">Safe DOM operations</h3>

<p>MV3’s strict CSP means you cannot use <code class="language-plaintext highlighter-rouge">element.innerHTML</code> to inject content. All DOM operations in Stained Wall use <code class="language-plaintext highlighter-rouge">DOMParser</code> instead:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Safe alternative using DOMParser</span>
<span class="kd">const</span> <span class="nx">parser</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">DOMParser</span><span class="p">();</span>
<span class="kd">const</span> <span class="nx">doc</span> <span class="o">=</span> <span class="nx">parser</span><span class="p">.</span><span class="nf">parseFromString</span><span class="p">(</span><span class="nx">html</span><span class="p">,</span> <span class="dl">'</span><span class="s1">text/html</span><span class="dl">'</span><span class="p">);</span>
<span class="k">while </span><span class="p">(</span><span class="nx">doc</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nx">firstChild</span><span class="p">)</span> <span class="p">{</span>
  <span class="nx">element</span><span class="p">.</span><span class="nf">appendChild</span><span class="p">(</span><span class="nx">doc</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nx">firstChild</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This pattern is more verbose but eliminates any risk of XSS and keeps the extension compliant with MV3’s security model.</p>

<h3 id="the-typescript-interface-problem">The TypeScript interface problem</h3>

<p>One practical challenge I ran into while building Stained Wall: when you compile Rust to WASM with <a href="https://wasm-bindgen.github.io/wasm-bindgen/"><code class="language-plaintext highlighter-rouge">wasm-bindgen</code></a>, you get auto-generated TypeScript bindings. But there is a catch: the boundary between Rust and JavaScript is limited to simple types. You cannot pass a rich Rust struct directly to JavaScript. Instead, you serialise to JSON on the Rust side and deserialise on the JavaScript side.</p>

<p>This means you end up maintaining parallel type definitions:</p>

<p>Rust side (<code class="language-plaintext highlighter-rouge">plan.rs</code>):</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">#[derive(Serialize)]</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="n">BypassPlan</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="n">site_name</span><span class="p">:</span> <span class="nb">String</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">hide_selectors</span><span class="p">:</span> <span class="nb">Vec</span><span class="o">&lt;</span><span class="nb">String</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">block_script_patterns</span><span class="p">:</span> <span class="nb">Vec</span><span class="o">&lt;</span><span class="nb">String</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">spoof_useragent</span><span class="p">:</span> <span class="nb">Option</span><span class="o">&lt;</span><span class="nb">String</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">fetch_archive</span><span class="p">:</span> <span class="nb">Option</span><span class="o">&lt;</span><span class="n">ArchivePlan</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="c1">// ...</span>
<span class="p">}</span>
</code></pre></div></div>

<p>TypeScript side (<code class="language-plaintext highlighter-rouge">types.ts</code>):</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kr">interface</span> <span class="nx">BypassPlan</span> <span class="p">{</span>
  <span class="nl">site_name</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
  <span class="nl">hide_selectors</span><span class="p">:</span> <span class="kr">string</span><span class="p">[];</span>
  <span class="nl">block_script_patterns</span><span class="p">:</span> <span class="kr">string</span><span class="p">[];</span>
  <span class="nl">spoof_useragent</span><span class="p">:</span> <span class="kr">string</span> <span class="o">|</span> <span class="kc">null</span><span class="p">;</span>
  <span class="nl">fetch_archive</span><span class="p">:</span> <span class="nx">ArchivePlan</span> <span class="o">|</span> <span class="kc">null</span><span class="p">;</span>
  <span class="c1">// ...</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The JSON bridge is simple and debuggable — you can inspect the data flowing between Rust and TypeScript in the browser console — but keeping these types in sync is a manual process.</p>

<pre><code class="language-mermaid">sequenceDiagram
    participant Rust as Rust (WASM)
    participant JSON as JSON bridge
    participant TS as TypeScript

    Rust-&gt;&gt;JSON: serde_json::to_string(&amp;plan)
    JSON-&gt;&gt;TS: JSON.parse(planJson) as BypassPlan
    Note over JSON: Simple, debuggable,&lt;br/&gt;but types must stay in sync
</code></pre>

<h2 id="zed-mjml-a-code-editor-extension-i-built-powered-by-wasm">Zed MJML: a code editor extension I built, powered by WASM</h2>

<h3 id="what-is-mjml">What is MJML?</h3>

<p><a href="https://mjml.io/">MJML</a> is an email markup language that compiles to responsive HTML email. If you have ever tried to write HTML emails by hand — with their nested tables, inline styles, and inconsistent client support — you will appreciate why MJML exists. It provides a component-based syntax that handles the responsive email nightmare for you:</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;mjml&gt;</span>
  <span class="nt">&lt;mj-body&gt;</span>
    <span class="nt">&lt;mj-section&gt;</span>
      <span class="nt">&lt;mj-column&gt;</span>
        <span class="nt">&lt;mj-text&gt;</span>Hello world<span class="nt">&lt;/mj-text&gt;</span>
        <span class="nt">&lt;mj-button</span> <span class="na">href=</span><span class="s">"https://example.com"</span><span class="nt">&gt;</span>Click me<span class="nt">&lt;/mj-button&gt;</span>
      <span class="nt">&lt;/mj-column&gt;</span>
    <span class="nt">&lt;/mj-section&gt;</span>
  <span class="nt">&lt;/mj-body&gt;</span>
<span class="nt">&lt;/mjml&gt;</span>
</code></pre></div></div>

<p>I used to do this by hand in Ye Old days (2015).</p>

<h3 id="why-i-built-a-zed-extension">Why I built a Zed extension</h3>

<p><a href="https://zed.dev/">Zed</a> is a modern code editor built in Rust, designed for speed and collaboration. Its extension system uses WASM as the plugin format — every extension is a compiled <code class="language-plaintext highlighter-rouge">.wasm</code> module that runs in a sandbox. I wanted proper MJML support in my editor, so I built <a href="https://github.com/pataruco/zed-mjml">zed-mjml</a> — a language server extension that provides diagnostics, validation, and previews. This is where things get interesting and where the contrast with browser extensions becomes clear.</p>

<h3 id="the-zed-extension-api">The Zed extension API</h3>

<p>Zed extensions implement a Rust trait that gets compiled to WASM:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">use</span> <span class="n">zed_extension_api</span> <span class="k">as</span> <span class="n">zed</span><span class="p">;</span>

<span class="k">struct</span> <span class="n">MjmlExtension</span> <span class="p">{</span>
    <span class="n">cached_binary_path</span><span class="p">:</span> <span class="nb">Option</span><span class="o">&lt;</span><span class="nb">String</span><span class="o">&gt;</span><span class="p">,</span>
<span class="p">}</span>

<span class="k">impl</span> <span class="nn">zed</span><span class="p">::</span><span class="n">Extension</span> <span class="k">for</span> <span class="n">MjmlExtension</span> <span class="p">{</span>
    <span class="k">fn</span> <span class="nf">new</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="k">Self</span> <span class="p">{</span>
        <span class="n">MjmlExtension</span> <span class="p">{</span> <span class="n">cached_binary_path</span><span class="p">:</span> <span class="nb">None</span> <span class="p">}</span>
    <span class="p">}</span>

    <span class="k">fn</span> <span class="nf">language_server_command</span><span class="p">(</span>
        <span class="o">&amp;</span><span class="k">mut</span> <span class="k">self</span><span class="p">,</span>
        <span class="n">_language_server_id</span><span class="p">:</span> <span class="o">&amp;</span><span class="nn">zed</span><span class="p">::</span><span class="n">LanguageServerId</span><span class="p">,</span>
        <span class="n">worktree</span><span class="p">:</span> <span class="o">&amp;</span><span class="nn">zed</span><span class="p">::</span><span class="n">Worktree</span><span class="p">,</span>
    <span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="nn">zed</span><span class="p">::</span><span class="n">Command</span><span class="o">&gt;</span> <span class="p">{</span>
        <span class="c1">// Download and return path to mjml-lsp binary</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="nn">zed</span><span class="p">::</span><span class="nd">register_extension!</span><span class="p">(</span><span class="n">MjmlExtension</span><span class="p">);</span>
</code></pre></div></div>

<p>The API provides a set of platform functions that extensions can call:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">zed::current_platform()</code> — detect OS and architecture</li>
  <li><code class="language-plaintext highlighter-rouge">zed::latest_github_release()</code> — fetch release metadata from GitHub</li>
  <li><code class="language-plaintext highlighter-rouge">zed::download_file()</code> — download files with gzip decompression</li>
  <li><code class="language-plaintext highlighter-rouge">zed::make_file_executable()</code> — set the executable bit</li>
  <li><code class="language-plaintext highlighter-rouge">zed::set_language_server_installation_status()</code> — show progress in the UI</li>
</ul>

<h3 id="the-zed-pattern">The Zed pattern</h3>

<p>Here is where WASM usage fundamentally differs from Stained Wall. In the browser extension, WASM runs the core logic. In the Zed extension, WASM is just a bootstrap layer. The extension’s sole job is to:</p>

<ol>
  <li>Check for the latest release of the <code class="language-plaintext highlighter-rouge">mjml-lsp</code> binary on GitHub.</li>
  <li>Download the correct platform-specific binary (macOS arm64, macOS x86_64, Linux x86_64)</li>
  <li>Make it executable</li>
  <li>Tell Zed where to find it.</li>
</ol>

<p>All the actual work — parsing MJML, validating documents, reporting diagnostics — happens in a native Rust binary that communicates with Zed over the <a href="https://microsoft.github.io/language-server-protocol/">Language Server Protocol (LSP)</a>.</p>

<pre><code class="language-mermaid">flowchart LR
    subgraph "WASM sandbox"
        A[Extension loads] --&gt; B[Check GitHub releases]
        B --&gt; C[Download mjml-lsp binary]
        C --&gt; D[Return binary path to Zed]
    end

    subgraph "Native process"
        E[mjml-lsp starts] --&gt; F[Listen on stdio]
        F --&gt; G[Receive document changes]
        G --&gt; H[Validate MJML]
        H --&gt; I[Send diagnostics back]
    end

    D --&gt; E

    style A fill:#f9d71c,stroke:#333,color:#000
    style E fill:#4ecdc4,stroke:#333,color:#000
</code></pre>

<h3 id="what-is-a-language-server">What is a language server?</h3>

<p>A language server is a separate process that provides language-specific features to an editor: diagnostics (errors and warnings), autocompletion, go-to-definition, hover information, and more. The Language Server Protocol (LSP) standardises the communication between the editor and the server, so the same language server can work with VS Code, Zed, Neovim, or any editor that supports LSP.</p>

<p>The <code class="language-plaintext highlighter-rouge">mjml-lsp</code>, server communicates over stdio (standard input/output) using JSON-RPC messages:</p>

<pre><code class="language-mermaid">sequenceDiagram
    participant Zed as Zed Editor
    participant LSP as mjml-lsp (native)

    Zed-&gt;&gt;LSP: textDocument/didOpen
    LSP-&gt;&gt;Zed: textDocument/publishDiagnostics
    Zed-&gt;&gt;LSP: textDocument/didChange
    LSP-&gt;&gt;Zed: textDocument/publishDiagnostics
    Zed-&gt;&gt;LSP: textDocument/didClose
    LSP-&gt;&gt;Zed: textDocument/publishDiagnostics (clear)
</code></pre>

<h3 id="tree-sitter-syntax-without-a-custom-grammar">Tree-sitter: syntax without a custom grammar</h3>

<p>One of the elegant decisions in the MJML extension is reusing the existing <a href="https://github.com/tree-sitter/tree-sitter-html">tree-sitter-html</a> grammar. Since MJML is syntactically identical to HTML — just with custom tag names like <code class="language-plaintext highlighter-rouge">&lt;mj-section&gt;</code> and <code class="language-plaintext highlighter-rouge">&lt;mj-text&gt;</code> — the HTML parser handles it perfectly.</p>

<p>Tree-sitter is an incremental parsing library that generates a concrete syntax tree from source code. Zed uses it for syntax highlighting, bracket matching, indentation, and code folding. Instead of writing a custom MJML grammar, the extension uses query files (<code class="language-plaintext highlighter-rouge">.scm</code> files written in S-expressions) to teach Zed how to treat MJML-specific tags:</p>

<div class="language-scheme highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">;; highlights.scm — Structural tags highlighted as keywords</span>
<span class="p">((</span><span class="nf">tag_name</span><span class="p">)</span> <span class="nv">@keyword</span>
  <span class="p">(</span><span class="o">#</span><span class="nv">match?</span> <span class="nv">@keyword</span> <span class="s">"^(mjml|mj-head|mj-body|mj-include)$"</span><span class="p">))</span>

<span class="c1">;; Head configuration tags highlighted as types</span>
<span class="p">((</span><span class="nf">tag_name</span><span class="p">)</span> <span class="nv">@type</span>
  <span class="p">(</span><span class="o">#</span><span class="nv">match?</span> <span class="nv">@type</span> <span class="s">"^(mj-attributes|mj-style|mj-font)$"</span><span class="p">))</span>

<span class="c1">;; CSS injection inside &lt;mj-style&gt; tags</span>
<span class="p">(</span><span class="nf">element</span>
  <span class="p">(</span><span class="nf">start_tag</span> <span class="p">(</span><span class="nf">tag_name</span><span class="p">)</span> <span class="nv">@_tag</span> <span class="p">(</span><span class="o">#</span><span class="nv">eq?</span> <span class="nv">@_tag</span> <span class="s">"mj-style"</span><span class="p">))</span>
  <span class="p">(</span><span class="nf">text</span><span class="p">)</span> <span class="nv">@injection</span><span class="o">.</span><span class="nv">content</span>
  <span class="p">(</span><span class="o">#</span><span class="nv">set!</span> <span class="nv">injection</span><span class="o">.</span><span class="nv">language</span> <span class="s">"css"</span><span class="p">))</span>
</code></pre></div></div>

<p>This approach means the extension gets syntax highlighting, bracket matching, auto-indentation, and CSS injection inside <code class="language-plaintext highlighter-rouge">&lt;mj-style&gt;</code> blocks — all without writing a single line of grammar code.</p>

<h3 id="the-validation-pipeline">The validation pipeline</h3>

<p>The language server uses a two-pass validation strategy:</p>

<p>Pass 1 — Custom semantic validation: A lightweight byte-level scanner extracts tags and their relationships, then four rules check against the MJML specification:</p>

<ol>
  <li>Nesting validation: <code class="language-plaintext highlighter-rouge">&lt;mj-text&gt;</code> must be inside <code class="language-plaintext highlighter-rouge">&lt;mj-column&gt;</code> or <code class="language-plaintext highlighter-rouge">&lt;mj-hero&gt;</code>, not directly in <code class="language-plaintext highlighter-rouge">&lt;mj-section&gt;</code></li>
  <li>Required attributes: <code class="language-plaintext highlighter-rouge">&lt;mj-image&gt;</code> must have a <code class="language-plaintext highlighter-rouge">src</code> attribute</li>
  <li>Unknown tag detection: Warns about typos like <code class="language-plaintext highlighter-rouge">&lt;mj-seciton&gt;</code> and suggests <code class="language-plaintext highlighter-rouge">&lt;mj-section&gt;</code> (using Levenshtein distance)</li>
  <li>Singleton enforcement: Only one <code class="language-plaintext highlighter-rouge">&lt;mj-head&gt;</code> and one <code class="language-plaintext highlighter-rouge">&lt;mj-body&gt;</code> per document</li>
</ol>

<p>Pass 2 — Structural validation: The <a href="https://crates.io/crates/mrml"><code class="language-plaintext highlighter-rouge">mrml</code></a> crate (a Rust MJML parser) performs deep structural validation, catching issues like unclosed tags and malformed XML.</p>

<pre><code class="language-mermaid">flowchart TB
    A[Document changed] --&gt; B[Scanner: extract tags]
    B --&gt; C[Rule 1: Nesting]
    B --&gt; D[Rule 2: Required attributes]
    B --&gt; E[Rule 3: Unknown tags]
    B --&gt; F[Rule 4: Singletons]
    A --&gt; G[mrml parser]
    C --&gt; H[Collect all diagnostics]
    D --&gt; H
    E --&gt; H
    F --&gt; H
    G --&gt; H
    H --&gt; I[Send to editor]
</code></pre>

<p>Both passes run independently, and all diagnostics are reported together, giving the developer a complete picture of issues in their MJML document.</p>

<h2 id="differences-between-zed-and-browser-extension-apis">Differences between Zed and browser extension APIs</h2>

<p>Having built WASM extensions for both platforms, the differences are striking:</p>

<table>
  <thead>
    <tr>
      <th>Aspect</th>
      <th>Firefox Extension (MV3)</th>
      <th>Zed Extension</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>WASM role</td>
      <td>Core logic engine (URL matching, plan generation)</td>
      <td>Bootstrap only (binary download and management)</td>
    </tr>
    <tr>
      <td>Runtime</td>
      <td>Browser’s WASM runtime (SpiderMonkey)</td>
      <td>Zed’s embedded WASM runtime (Wasmtime)</td>
    </tr>
    <tr>
      <td>Host APIs</td>
      <td>Browser APIs via JavaScript (<code class="language-plaintext highlighter-rouge">browser.*</code>)</td>
      <td>Zed APIs via <code class="language-plaintext highlighter-rouge">zed_extension_api</code> crate</td>
    </tr>
    <tr>
      <td>Communication</td>
      <td>JSON over message passing</td>
      <td>Language Server Protocol over stdio</td>
    </tr>
    <tr>
      <td>Build target</td>
      <td><code class="language-plaintext highlighter-rouge">wasm32-unknown-unknown</code> via <code class="language-plaintext highlighter-rouge">wasm-pack</code></td>
      <td><code class="language-plaintext highlighter-rouge">wasm32-wasip1</code> via <code class="language-plaintext highlighter-rouge">cargo build</code></td>
    </tr>
    <tr>
      <td>Security model</td>
      <td>CSP + <code class="language-plaintext highlighter-rouge">wasm-unsafe-eval</code></td>
      <td>WASI sandbox (no file system, no network)</td>
    </tr>
    <tr>
      <td>Extension language</td>
      <td>Rust (WASM) + TypeScript (browser APIs)</td>
      <td>Pure Rust (WASM + native binary)</td>
    </tr>
    <tr>
      <td>Update mechanism</td>
      <td>Manual rebuild and reload</td>
      <td>Zed extension marketplace + GitHub releases</td>
    </tr>
  </tbody>
</table>

<p>The most significant difference is in what the WASM module actually does. In the browser extension, WASM is the brain — it makes decisions that drive the extension’s behaviour. In the Zed extension, WASM is the hand — it performs a mechanical task (downloading a binary) and then steps aside.</p>

<p>This difference comes down to platform constraints:</p>

<ul>
  <li>
    <p>Browser extensions have access to rich JavaScript APIs but need WASM for performance and safety. The WASM module can do substantial work because the browser provides a full JavaScript runtime alongside it.</p>
  </li>
  <li>
    <p>Zed extensions run in a stricter WASI sandbox with limited capabilities. The WASM module can call Zed’s API functions but cannot spawn processes, open network connections, or access the file system directly. For anything beyond simple logic, the pattern is to delegate to a native binary via LSP.</p>
  </li>
</ul>

<pre><code class="language-mermaid">graph TB
    subgraph "Browser Extension Pattern"
        direction TB
        BW[WASM Engine] --&gt;|JSON plan| BTS[TypeScript]
        BTS --&gt;|browser.* APIs| BDOM[DOM / Network]
        style BW fill:#f9d71c,stroke:#333,color:#000
    end

    subgraph "Zed Extension Pattern"
        direction TB
        ZW[WASM Bootstrap] --&gt;|binary path| ZLSP[Native LSP Binary]
        ZLSP --&gt;|LSP protocol| ZED[Zed Editor]
        style ZW fill:#f9d71c,stroke:#333,color:#000
        style ZLSP fill:#4ecdc4,stroke:#333,color:#000
    end
</code></pre>

<h2 id="what-i-learned">What I learned</h2>

<p>Building these two projects taught me that WASM is not a single thing — it is a spectrum of integration patterns. At one end, you have heavy WASM modules that perform substantial computation. At the other end, you have thin WASM wrappers that exist purely to satisfy a platform’s extension format (like the Zed MJML extension).</p>

<p>The key is understanding what the host platform offers and what it constrains:</p>

<ul>
  <li>
    <p>If the host gives you rich APIs and a JavaScript runtime, lean into WASM for the logic that benefits from Rust’s safety and performance.</p>
  </li>
  <li>
    <p>If the host gives you a strict sandbox with limited capabilities, use WASM as a bridge to native code where the real work happens.</p>
  </li>
</ul>

<p>WASM’s portability promise is real, but it is not about running the same code everywhere in the same way. It is about having a safe, sandboxed format that platforms can adopt as their extension mechanism — and then building the right architecture around it.</p>

<p>If you have not tried WASM yet, I would encourage you to start small. Pick a piece of logic that is well-defined and self-contained — a validator, a parser, a matcher — compile it to WASM, and see how it feels. You might be surprised at how natural it is to have Rust and TypeScript working side by side.</p>

<hr />

<p><em>It is an open source project: <a href="https://github.com/pataruco/zed-mjml">zed-mjml</a>.</em></p>]]></content><author><name>Pedro Martin</name></author><category term="pataruco" /><summary type="html"><![CDATA[WebAssembly is a spectrum from a heavy decision engine in a browser extension to a thin bootstrap layer in a code editor plugin. Two projects, two patterns, one compilation target.]]></summary></entry><entry><title type="html">Adding File I/O to the SHA256 Hash Algorithm Implemented in WebAssembly Text</title><link href="https://awesome.red-badger.com//chriswhealy/sha256-extended" rel="alternate" type="text/html" title="Adding File I/O to the SHA256 Hash Algorithm Implemented in WebAssembly Text" /><published>2025-01-30T12:00:00+00:00</published><updated>2025-01-30T12:00:00+00:00</updated><id>https://awesome.red-badger.com//chriswhealy/sha256-extended</id><content type="html" xml:base="https://awesome.red-badger.com//chriswhealy/sha256-extended"><![CDATA[<h1 id="prerequisites">Prerequisites</h1>

<p>Before diving into this blog, please check that the following prerequisites have been met.</p>

<ol>
  <li>
    <p>Are you comfortable writing directly in WebAssembly Text? (Be honest)</p>

    <p>If the answer is “No”, then please read my <a href="/chriswhealy/Introduction%20to%20WebAssembly%20Text">Introduction to WebAssembly Text</a></p>
  </li>
  <li>
    <p>Install <a href="https://wasmtime.dev/"><code class="language-plaintext highlighter-rouge">wasmtime</code></a>.
This is an Open Source project by the Bytecode Alliance that provides both the WebAssembly development tools we will be using, and the WebAssembly System Interface (WASI) that will be the focus of our attention in this blog.</p>
  </li>
  <li>
    <p>In order to understand how to code against the WASI interface, it is very helpful to look at the Rust source code that implements the WASI functions you will be calling from your WebAssembly Text program.</p>

    <p>This code can be found in the <code class="language-plaintext highlighter-rouge">wasmtime</code> GitHub repo <a href="https://github.com/bytecodealliance/wasmtime">https://github.com/bytecodealliance/wasmtime</a>.
The specific file to look in is <code class="language-plaintext highlighter-rouge">crates/wasi-preview1-component-adapter/src/lib.rs</code></p>
  </li>
</ol>

<h1 id="explanation-of-update">Explanation of Update</h1>

<p>The original version of this program focused only on implementation the core the SHA256 algorithm.
This was a good starting point, but it meant that the JavaScript wrapper used to invoke the WASM module had to read the file from disk, then write it to memory in the special format required by the SHA256 algorithm.</p>

<p>The purpose of this update therefore is to significantly reduce the degree of coupling between the JavaScript wrapper and the underlying WASM module by moving all the file IO into WebAssembly.</p>

<h1 id="overview-of-steps">Overview of Steps</h1>

<ol>
  <li><a href="/chriswhealy/sha256-extended/00-getting-started/">Getting Started</a></li>
  <li><a href="/chriswhealy/sha256-extended/10-start-wasi/">Start WASI</a></li>
  <li><a href="/chriswhealy/sha256-extended/20-import-wasi/">Import WASI Functions into WebAssembly</a></li>
  <li><a href="/chriswhealy/sha256-extended/30-count-cmd-line-args/">Count the Command Line Arguments</a></li>
  <li><a href="/chriswhealy/sha256-extended/40-parse-cmd-line-args/">Extract the filename from the command line arguments</a></li>
  <li><a href="/chriswhealy/sha256-extended/50-open-file/">Open the file</a></li>
  <li><a href="/chriswhealy/sha256-extended/60-read-file-size/">Read the File Size</a></li>
  <li><a href="/chriswhealy/sha256-extended/70-grow-memory/">Do We Have Enough Memory?</a></li>
  <li><a href="/chriswhealy/sha256-extended/80-read-file/">Read the file into memory</a></li>
  <li><a href="/chriswhealy/sha256-extended/90-close-file/">Close the file</a></li>
</ol>

<h1 id="extras">Extras</h1>

<ol>
  <li><a href="/chriswhealy/sha256-extended/wat_tips_and_tricks/">WebAssembly Coding Tips and Tricks</a></li>
  <li><a href="/chriswhealy/sha256-extended/debugging_wasm/">Debugging WASM</a></li>
</ol>]]></content><author><name>Chris Whealy</name></author><category term="chriswhealy" /><summary type="html"><![CDATA[My original WebAssembly Text implementation of the SHA256 algorithm focused only on the core functionality. The WAT program has now been extended to include all file I/O.]]></summary></entry><entry><title type="html">Hieroglyphy: Taking JavaScript Type Coercion to its Illogical Conclusion</title><link href="https://awesome.red-badger.com//chriswhealy/hieroglyphy" rel="alternate" type="text/html" title="Hieroglyphy: Taking JavaScript Type Coercion to its Illogical Conclusion" /><published>2023-03-17T12:00:00+00:00</published><updated>2023-03-17T12:00:00+00:00</updated><id>https://awesome.red-badger.com//chriswhealy/hieroglyphy</id><content type="html" xml:base="https://awesome.red-badger.com//chriswhealy/hieroglyphy"><![CDATA[<h1 id="table-of-contents">Table of Contents</h1>

<ul>
  <li><a href="/chriswhealy/hieroglyphy/but-why/">But Why?</a></li>
  <li><a href="/chriswhealy/hieroglyphy/bootstraps/">Pulling Ourselves Up By Our Bootstraps</a></li>
  <li><a href="/chriswhealy/hieroglyphy/strings/">Pulling Some Strings</a></li>
  <li><a href="/chriswhealy/hieroglyphy/checkpoint1/">What Have We Achieved So Far?</a></li>
  <li><a href="/chriswhealy/hieroglyphy/keywords/">Extracting Characters From Keywords</a></li>
  <li><a href="/chriswhealy/hieroglyphy/numbers/">Tricks With Big Numbers</a></li>
  <li><a href="/chriswhealy/hieroglyphy/checkpoint2/">So Where Are We Now?</a></li>
  <li><a href="/chriswhealy/hieroglyphy/functions/">Tricks With Functions</a></li>
</ul>

<h1 id="introduction">Introduction</h1>

<p>The functionality described in this blog is neither new nor is it unique.
In this case, it is an extensive rewrite and optimisation of the original <a href="https://github.com/alcuadrado/hieroglyphy">Hieroglyphy</a> by <a href="https://github.com/alcuadrado/">Patricio Palladino</a>.</p>

<p>Here is my version of <a href="https://github.com/ChrisWhealy/hieroglyphy">Hieroglyphy</a>.</p>

<p><a href="https://github.com/aemkei/jsfuck">Other variations</a> of this style of app exist that use a minimal alphabet, but in this particular case, a close-to-minimal alphabet has been chosen.</p>

<blockquote>
  <p><strong><em>WARNING</em></strong><br />
I can think of no practical reason why you would ever want to use this library in a real life situation…</p>

  <p>🤪</p>

  <p>But that said, the process by which it works is interesting if you really want to understand the inner workings of JavaScript’s type coercion behaviour</p>
</blockquote>

<h1 id="overview">Overview</h1>

<p>There has been some investigation into encoding the source code of a JavaScript program such that it uses a reduced alphabet, but remains syntactically valid and executable.</p>

<p>The object of the exercise here is not to create a program that remains human readable, but one that can be <code class="language-plaintext highlighter-rouge">eval</code>ed and executed.</p>

<p>For example:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">$</span> <span class="nx">node</span>
<span class="nx">Welcome</span> <span class="nx">to</span> <span class="nx">Node</span><span class="p">.</span><span class="nx">js</span> <span class="nx">v16</span><span class="p">.</span><span class="mf">12.0</span><span class="p">.</span>
<span class="nx">Type</span> <span class="dl">"</span><span class="s2">.help</span><span class="dl">"</span> <span class="k">for</span> <span class="nx">more</span> <span class="nx">information</span><span class="p">.</span>
<span class="o">&gt;</span> <span class="nf">eval</span><span class="p">(</span><span class="dl">'</span><span class="s1">(+((+!![]+[])+(!![]+!![]+!![]+!![]+!![]+!![]+!![]+[])))[(!![]+[])[+[]]+([]+{})[+!![]]+([]+([]+{})[([]+{})[!![]+!![]+!![]+!![]+!![]]+([]+{})[+!![]]+([][+[]]+[])[+!![]]+(![]+[])[!![]+!![]+!![]]+(!![]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+[]]+([]+{})[!![]+!![]+!![]+!![]+!![]]+(!![]+[])[+[]]+([]+{})[+!![]]+(!![]+[])[+!![]]])[!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]]+(!![]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[!![]+!![]+!![]+!![]+!![]]+([][+[]]+[])[+!![]]+([]+([]+{})[([]+{})[!![]+!![]+!![]+!![]+!![]]+([]+{})[+!![]]+([][+[]]+[])[+!![]]+(![]+[])[!![]+!![]+!![]]+(!![]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+[]]+([]+{})[!![]+!![]+!![]+!![]+!![]]+(!![]+[])[+[]]+([]+{})[+!![]]+(!![]+[])[+!![]]])[+((+!![]+[])+(!![]+!![]+!![]+!![]+[]))]](+((!![]+!![]+!![]+[])+(!![]+!![]+!![]+!![]+!![]+!![]+[])))+(!![]+[])[!![]+!![]+!![]]+(![]+[])[!![]+!![]]+(![]+[])[!![]+!![]]+([]+{})[+!![]]</span><span class="dl">'</span><span class="p">)</span>
<span class="dl">'</span><span class="s1">hello</span><span class="dl">'</span>
<span class="o">&gt;</span>
</code></pre></div></div>

<h1 id="room-from-improvement">Room from improvement</h1>

<p>This library is just a proof of concept; there is plenty of room for improvement:</p>

<ul>
  <li>Ensure that all characters have been encoded using the shortest possible representation</li>
  <li>Allow some flexibility in the alphabet size to account for different target runtime environments</li>
  <li>Knowing the specific runtime would allow us to take advantage of features unique to that environment, which in turn, may yield shorter character encodings</li>
</ul>]]></content><author><name>Chris Whealy</name></author><category term="chriswhealy" /><summary type="html"><![CDATA[JavaScript is (in)famous for being a highly dynamic language that allows a developer to write very "flexible" code. One language feature that makes a significant contribution to this flexibility is the idea of type coercion; that is, JavaScript will automatically (and silently) transform a value of one type into a value of a different type.As you can imagine however, the more you explore the language's flexibility, the higher a price you pay in terms of code legibility.Largely for the sake of amusement, this blog takes JavaScript's flexibility to the most extreme (and illogical) conclusion by providing you with an encoding library that takes a regular JavaScript program as input and returns a very long character string that is executable, functionally identical, and completely unreadable!]]></summary></entry><entry><title type="html">Implementing the SHA256 Hash Algorithm in WebAssembly Text</title><link href="https://awesome.red-badger.com//chriswhealy/sha256-webassembly" rel="alternate" type="text/html" title="Implementing the SHA256 Hash Algorithm in WebAssembly Text" /><published>2023-02-20T12:00:00+00:00</published><updated>2023-02-20T12:00:00+00:00</updated><id>https://awesome.red-badger.com//chriswhealy/sha256-webassembly</id><content type="html" xml:base="https://awesome.red-badger.com//chriswhealy/sha256-webassembly"><![CDATA[<h2 id="table-of-contents">Table of Contents</h2>

<ul>
  <li><a href="/chriswhealy/sha256/algorithm-overview/">SHA256 Algorithm Overview</a></li>
  <li><a href="/chriswhealy/sha256/endianness/">WebAssembly Does Not Have A “raw binary” Data Type</a></li>
  <li><a href="/chriswhealy/sha256/architecture/">WebAssembly Program Architecture</a></li>
  <li><a href="/chriswhealy/sha256/implementation/">WebAssembly Implementation</a></li>
  <li><a href="/chriswhealy/sha256/testing/">Unit Testing WebAssembly Functions</a></li>
  <li><a href="/chriswhealy/sha256/host-environment/">JavaScript Host Environment</a></li>
  <li><a href="/chriswhealy/sha256/summary/">Summary</a></li>
</ul>

<h2 id="development-objectives">Development Objectives</h2>

<ol>
  <li>See how small a binary can be produced when the SHA256 digest algorithm is implemented directly in WebAssembly Text</li>
  <li>Compare the runtime performance of the WebAssembly module with the native <code class="language-plaintext highlighter-rouge">sha256sum</code> program supplied with macOS</li>
</ol>

<p>The Git repo containing the working software can be <a href="https://github.com/ChrisWhealy/wasm_sha256">found here</a></p>

<h2 id="development-challenges">Development Challenges</h2>

<p>Two challenges had to be overcome during development:</p>

<ol>
  <li>The SHA256 algorithm expects to handle data in network byte order, but WebAssembly only has numeric data types that automatically rearrange a value’s byte order according to the CPU’s endianness.</li>
  <li>Unit testing WASM functions within a module is an entirely manual process.
This presented an interesting challenge - especially when writing unit tests for private WASM functions</li>
</ol>]]></content><author><name>Chris Whealy</name></author><category term="chriswhealy" /><summary type="html"><![CDATA[WebAssembly Text (WAT) is ideally suited for implementing CPU intensive algorithms such as calculating a file's SHA256 hash. This blog describes not only how I got this algorithm working in WebAssembly Text, but takes a wider view and looks at the areas where improvements could be made in the overall developer experience of working with WAT.]]></summary></entry><entry><title type="html">Quick notes on how to be a Principal Dev</title><link href="https://awesome.red-badger.com//timlee/principal-dev-conference" rel="alternate" type="text/html" title="Quick notes on how to be a Principal Dev" /><published>2022-12-16T12:00:00+00:00</published><updated>2022-12-16T12:00:00+00:00</updated><id>https://awesome.red-badger.com//timlee/principal-dev-conference</id><content type="html" xml:base="https://awesome.red-badger.com//timlee/principal-dev-conference"><![CDATA[<h1 id="quick-notes-on-how-to-be-a-principal-dev">Quick notes on how to be a Principal Dev</h1>

<p><em><a href="../">Tim Lee</a> — 16 December 2022</em></p>

<h2 id="introduction">Introduction</h2>
<p>Short notes from a 2-day <a href="https://principal.dev/">Principal Dev</a> training I attended, lead by Eduards Sizovs.</p>

<p>Full course slides can be found <a href="https://sizovs.net/principal/#1">here</a> (arrow to navigate).</p>

<h2 id="short-notes">Short notes</h2>

<h3 id="agile">Agile</h3>
<ul>
  <li>Agile manifesto says “Our highest priority is to satisfy the customer through early and continuous delivery of valuable software”. But customers don’t <em>need</em> software, they need cost-efficient solutions to their problems. So perhaps it should be reworded to: “Our highest priority is to find cost-efficient solutions to customers’ problems”</li>
  <li>Process of finding the cost-efficient solution from a list of options is called Cost-Benefit Analysis (CBA). Parameters to consider - speed, cost, benefit, risk</li>
  <li>Disney brainstorming method good for sourcing ideas as a group
    <ol>
      <li>Phase 1: gather all ideas, acting as “Dreamers”</li>
      <li>Phase 2: assess ideas as “Realists” looking for limitations</li>
      <li>Phase 3: analyze ideas as “Critics”, addressing possible risks</li>
    </ol>
  </li>
  <li>Don’t do what a customer says they want, do what a customer needs (cost-efficiently). Should solve the problem, not the want.</li>
  <li>Important not to shield developers from the business, because software is the implementation <strong>of</strong> the business. Shielding leads to low trust, low motivation and mediocre software solutions.
    <ul>
      <li>To find cost-efficient solutions, you need a whole-team approach.</li>
      <li>To write great code, you need a deep understanding of the underlying problem. (software is the implementation of the problem domain. Weak problem understanding -&gt; weak solutions)</li>
      <li>Understanding the business domain turns engineers into business partners</li>
      <li>The team owns the product, not product owners. Product ~owners~ leaders promote product understanding.</li>
      <li>Make impacts, not software</li>
    </ul>
  </li>
</ul>

<h3 id="leading">Leading</h3>
<ul>
  <li>As a leader your job depends on seeing things others don’t see. Can’t make yourself always busy - need head space to observe.</li>
  <li>Should switch focus to high-leverage activities (HLAs) - the activities with the highest return on your time investment. Leverage = impact / effort</li>
  <li>Your output is the output of your team. Being busy is not the same as being productive. So focus on HLAs (Pareto principle - 20% of activities give 80% of result). To notice HLAs, don’t overwhelm yourself with Low Level Activities (LLAs).</li>
  <li>To get rid of LLAs: Delete -&gt; Defer -&gt; Delegate -&gt; Do</li>
  <li>Removing impediments is good, but empowering the team to see, prioritise and remove it’s own impediments is better.</li>
</ul>

<h3 id="measuring-dev-teams-performance">Measuring dev team’s performance</h3>
<ul>
  <li>Lead time - time taken to solve a problem, or time a problem stays “in progress”
    <ul>
      <li>Can be reduced with cost efficient solutions, good architecture, full-cycle teams (autonomous, cross-functional, self-organising)</li>
    </ul>
  </li>
  <li>Defects</li>
  <li>DDP (due date performance) - ability to deliver on time</li>
  <li>Reliability
    <ul>
      <li>MTBF (mean time between failures), MTTD (mean time to detect), MTTR (mean time to remediate)</li>
    </ul>
  </li>
  <li>ROI - what can you offer that the market can’t? Cost-efficient solutions, cost reduction, innovation, leadership, mentoring, visibility, hiring</li>
  <li>Satisfaction - of customers and business
    <ul>
      <li>Ask 3 questions:
        <ol>
          <li>Are you happy with my work?</li>
          <li>What do I suck at?</li>
          <li>What can I do to improve?</li>
        </ol>
      </li>
    </ul>
  </li>
</ul>

<h3 id="throughput">Throughput</h3>
<ul>
  <li>Little’s law - lead time = work in progress / throughput</li>
  <li>Reducing WIP leads to work being delivered more quickly</li>
  <li>Focus as many people as possible on as few projects/stories/tasks as possible - swarming.</li>
  <li>4 ways to increase throughput:
    <ol>
      <li>Hire more people - though law of diminishing returns (adding more people beyond certain threshold is less efficient). Also Brooks law - adding manpower to late project makes it even later. Small teams tend to do better than large ones - more flexible, aligned, involved, engaged, better relationships, low management overhead. If need more people, continuously refactor company into a network of small, independent, full-cycle teams</li>
      <li>High-performance teams - aligned, small, diverse, full-cycle, stable (to achieve Tuckman’s stages of group development - forming, storming, norming, performing, takes about a year to get to last stage), skilled</li>
      <li>Technical excellence - engineers learn by example from seniors, so choose seniors and leaders carefully. Make sure they promote software craftmanship, clean code, extreme programming, TDD. Make mentoring a prerequisite for promotion.</li>
      <li>Reduce waste, which includes
        <ol>
          <li>Partially done work</li>
          <li>Overproduction</li>
          <li>Waiting/handoffs</li>
          <li>Rework</li>
          <li>Non-value added activities</li>
        </ol>
      </li>
    </ol>
  </li>
</ul>

<h3 id="tips-for-delivering-on-promises-namely-within-scrum">Tips for delivering on promises (namely, within Scrum)</h3>
<ul>
  <li>Know your velocity (work delivered without cutting corners)</li>
  <li>Even under pressure, don’t take more work than allowed by your velocity</li>
  <li>Remove planned work when unplanned work appears</li>
  <li>Minimise unplanned work</li>
  <li>Estimate with Fibonnaci numbers to gain speed at cost of accuracy</li>
  <li>Play planning poker until all team members estimates match to reduce HIPPO pressure</li>
  <li>When estimation is difficult or the problem is too big - split or spike to reduce unknowns</li>
  <li>Estimates are innacruate - comfortable velocity must include a buffer</li>
  <li>Turn on swarming for speed and continuous flow, monitor flow to notice issues</li>
  <li>Keep tech debt under control</li>
  <li>Process of estimation is more important than estimates - learn about business, understand problems better, cut scope, slice work for better flow, raise inconvenient questions like why estimates are inflated…</li>
</ul>

<h3 id="tech-debt">Tech debt</h3>
<ul>
  <li>There is a strong correlation between the quality of the codebase and throughput.</li>
  <li>A delivery team tries to maintain it’s velocity over time.</li>
  <li>Degredation of the codebase accelerates with time due to reinforcing loops - broken window theory, Brook’s law, Lehman’s law (entropy), Gresham’s law (bad code drives out good - you want to touch nice code, you don’t want to touch bad code so they stay longer and sometimes have abstractions written around them).</li>
  <li>Way to achieve sustainable pace/continuous improvement - every time you touch code try and make it slightly better, never worse than before (the Boyscout rule)</li>
  <li>Currency of technical debt is throughput - you borrow throughput and pay it back. It compounds, so the longer it takes to pay back, the worse it gets. Things get built on top of it. So want to pay technical debt early.</li>
  <li>Tech debt is a tool for short term gain.</li>
  <li>Explain the cost of tech debt when making the trade off so there’s an understanding that it will either slow down future delivery or be paid back soon.</li>
  <li>Eliminating the cause of technical debt is more important than eliminating the technical debt.</li>
  <li>4 main causes of tech debt:
    <ol>
      <li>Knowledge or skills problem - strong technical leadership/mentoring needed</li>
      <li>Attitude problem - “I don’t have time for quality”</li>
      <li>Borrowing</li>
      <li>Learning - knowing more at a later date, finding a better way to do it. So important to attract, develop, retain tech/domain knowledge, along with KISS, YAGNI, validate before building</li>
    </ol>
  </li>
</ul>

<h3 id="mentoring">Mentoring</h3>
<ul>
  <li>The learning pyramid goes from least effective to most effective - watching lectures -&gt; reading -&gt; audio/visual -&gt; demonstration -&gt; group discussion -&gt; practicing by doing -&gt; teaching others</li>
  <li>Pyramid moves from passive learning method to active learning methods</li>
  <li>Teaching others is most effective, notably even more than by doing, which is why mentoring is crucial</li>
  <li>4 stages of learning:
    <ol>
      <li>Accumulate knowledge (read, hear, practice)</li>
      <li>Teach, explain (code review, pairing, writing, presenting)</li>
      <li>Get feedback</li>
      <li>Deepen understanding (and improve how well you can articulate an idea)</li>
    </ol>
  </li>
  <li>If you can’t explain it, you don’t understand it</li>
</ul>

<h3 id="high-performing-teams">High performing teams</h3>
<ul>
  <li>Bus factor - make yourself replaceable. The more influential your role, the more you have to care about succession planning. Leaders grow leaders.</li>
  <li>Team performance = sum of performance of each individual x teamwork / wastes</li>
  <li>The wider the skill gap between you and your teammates, the more you have to mentor</li>
  <li>A team needs T-shape people, ensure people have some capability in all areas</li>
  <li>Retention more important than hiring - retain domain knowledge, benefit from growing them, don’t have cost of hiring</li>
  <li>2 types of motivation
    <ol>
      <li>Intrinsic - driven because you find it rewarding, performing an activity for its own sake, the behaviour itself is its own reward</li>
      <li>Extrinsic motivation - driven to earn a reward, get praise or avoid punishment. Do something because expect to get something in exchange</li>
    </ol>
  </li>
  <li>Intrinsic motivation is the optimum, more sustainable driver.</li>
  <li>Primary motivators:
    <ol>
      <li>Autonomy - freedom to make decisions, create workspace, choose what to work on. Not complete freedom, especially more junior members - not earned trust. More senior = more autonomy - more trust, more expertise.</li>
      <li>Mastery - becoming more capable, learning new skills</li>
      <li>Purpose - aligning work with personal goals</li>
    </ol>
  </li>
</ul>

<h3 id="recruiting">Recruiting</h3>
<ul>
  <li>Important to improve quality of inbound traffic</li>
  <li>Be visible, otherwise people will come to the interview just to find out who you are, waste of time</li>
  <li>Write ads that attract good candidates (with right attitude but lacking certain skills, or with imposter syndrome) and drives away bad candidates (right skills but lacking attitude)</li>
  <li>Perceived ability vs Actual ability graph. Imposter syndrome - high actual ability, low perceived ability. Dunning-Kruger - high perceived ability, low actual ability. A lot of job ads attracts Dunning-Kruger’s. Instead of “deep understanding”, “good knowledge of”, “+3 years of experience”, describe the environment, technology and what the person will be doing, what you tend to favour (pair programming, etc) and that it doesn’t matter if you don’t know something because they offer mentoring. Show job ads to as many colleageus as possible, not just the hiring manager/recruiter having written. textio.com - accessibility language checker</li>
  <li>Beat expectations
    <ul>
      <li>People should walk out of a conversation smarter, happier, energized (even if they don’t make the hire)</li>
      <li>Invite for a conversation, not an . Run as a conversation instead of a conversation.</li>
      <li>Start with a presentation</li>
      <li>Inspire with gift of books in the interview that are relevant - e.g. Clean Code</li>
    </ul>
  </li>
  <li>Let candidates code in their comfortable environment
    <ul>
      <li>The Hawthorne effect (Thinking, fast and slow) - when in stressful situation System 1 dominates and can’t think</li>
      <li>Homework is most inclusive way to assess tech skills</li>
      <li>Compact but representative</li>
      <li>Owe the candidate a code review (good parts, bad parts, suggestions, books)</li>
    </ul>
  </li>
</ul>

<h3 id="reciprocity">Reciprocity</h3>
<ul>
  <li>Reciprocity is a social norm of responding to a positive action with another positive action, rewarding kind actions. If you invest in someone, they’ll invest in you - invest wisely</li>
  <li>Important for:
    <ol>
      <li>Authority - brings influence</li>
      <li>Consistency - basic social contract, do what you say</li>
      <li>Liking - if people don’t like you, they’ll object to your ideas even if they’re rational</li>
      <li>Scarcity - things that are available for everyone have lower perceived value. Things that are not available for everyone have higher perceived value. If you have skills that are more in demand, they’ll be more valuable.</li>
      <li>Social proof - shortcut to make decisions based on things like ratings, reviews, recommendations, etc</li>
    </ol>
  </li>
</ul>

<h3 id="management-toolbox">Management toolbox</h3>
<ul>
  <li>Encourage - help people fight self-doubts
    <ul>
      <li>#1 reason people leave their role is lack of appreciation - just say thanks is not good, say thank you, “name” for doing “x”</li>
      <li>Support initiative</li>
      <li>Behind every request is an unfulfilled desire or need - ask why if don’t understand, try to get to bottom of need</li>
    </ul>
  </li>
  <li>Challenge - delegate technical and non-technical work
    <ul>
      <li>Delegate tasks that maximize learning (trade-off with speed)</li>
    </ul>
  </li>
  <li>Answer “how can I do X or Y” with “what options do you see?”</li>
  <li>Share:
    <ul>
      <li>Knowledge</li>
      <li>Energy - should know the source of your energy, understand what energises you</li>
    </ul>
  </li>
  <li>Set rules
    <ul>
      <li>Less control, more systems</li>
      <li>e.g. WIP limits, YBIYRI (You Build It, You Run It), CI with daily push into the , QA should find nothing (build quality in), every time new pair</li>
    </ul>
  </li>
  <li>What skills do my team-mates need the most?
    <ul>
      <li>At the intersection of personal career goals and team needs</li>
    </ul>
  </li>
  <li>Trust - product of:
    <ul>
      <li>Your knowledge</li>
      <li>Your consistency</li>
      <li>Your authority</li>
      <li>Your confidence</li>
      <li>Your charisma - energy, body language, voice, words</li>
      <li>Read: Nonviolent communication</li>
      <li>Your Emotional Intelligence (EQ) - managing thoughts, emotions, mind, ability to connect with others</li>
    </ul>
  </li>
</ul>

<h3 id="answering-the-question---am-i-a-good-leader">Answering the question - am I a good leader?</h3>
<ul>
  <li>Is your team successful?</li>
  <li>Is each individual in your team successful?</li>
  <li>Is your work appreciated by others?</li>
  <li>Is your team reporting high scores in weekly health checks (Spotify strategy - R/G/B scored against categories)?</li>
  <li>Is employee turnover low?</li>
  <li>Are people lining up to work with you?</li>
</ul>]]></content><author><name>Tim Lee</name></author><category term="timlee" /><summary type="html"><![CDATA[Do you want to be a principal dev? Look no further. These notes will sort you right out.]]></summary></entry><entry><title type="html">WebAssembly Memory Growth and the Detached ArrayBuffer Problem</title><link href="https://awesome.red-badger.com//chriswhealy/memory-grow-and-arraybuffers" rel="alternate" type="text/html" title="WebAssembly Memory Growth and the Detached ArrayBuffer Problem" /><published>2022-10-05T12:00:00+00:00</published><updated>2022-10-05T12:00:00+00:00</updated><id>https://awesome.red-badger.com//chriswhealy/memory-grow-and-arraybuffers</id><content type="html" xml:base="https://awesome.red-badger.com//chriswhealy/memory-grow-and-arraybuffers"><![CDATA[<h2 id="context">Context</h2>

<ul>
  <li>A WebAssembly module and its host environment can share a block of linear memory.</li>
  <li>If JavaScript acts as the host environment, then shared memory appears as a JavaScript <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer"><code class="language-plaintext highlighter-rouge">ArrayBuffer</code></a>.</li>
  <li>JavaScript cannot directly manipulate the contents of an <code class="language-plaintext highlighter-rouge">ArrayBuffer</code>.
 Instead, it must use some sort of overlay or mask such as a <code class="language-plaintext highlighter-rouge">Uint8Array</code> or a <code class="language-plaintext highlighter-rouge">Uint32Array</code>.
 Then the data in the <code class="language-plaintext highlighter-rouge">ArrayBuffer</code> can be accessed using the overlaid structure’s semantics.</li>
</ul>

<h2 id="problem-summary">Problem Summary</h2>

<p>A collision between these two facts creates the “Detached ArrayBuffer” problem:</p>

<ul>
  <li>JavaScript <code class="language-plaintext highlighter-rouge">ArrayBuffer</code>s are of fixed-length and once allocated, cannot be extended.</li>
  <li>WebAssembly linear memory can be extended by calling <a href="https://webassembly.github.io/spec/core/syntax/instructions.html#syntax-instr-memory"><code class="language-plaintext highlighter-rouge">memory.grow</code></a>.</li>
</ul>

<p>If WebAssembly memory grows,<sup id="fnref:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup> then the old JavaScript <code class="language-plaintext highlighter-rouge">ArrayBuffer</code> must be thrown away and a new one created.
Consequently, any JavaScript objects that used to overlay the old <code class="language-plaintext highlighter-rouge">ArrayBuffer</code> are immediately invalidated because they now point to nothing.
The floor has literally been pulled out from underneath these objects and they must all be redefined over top of the new <code class="language-plaintext highlighter-rouge">ArrayBuffer</code>.</p>

<p>There is a <a href="https://www.proposals.es/proposals/Resizable%20and%20growable%20ArrayBuffers">proposal</a> to allow a JavaScript <code class="language-plaintext highlighter-rouge">ArrayBuffer</code> to grow, and as soon as this functionality appears, this problem will disappear.</p>

<p>Meanwhile, back in Gotham City…</p>
<h2 id="what-consequences-do-these-facts-create-when-writing-in-rust">What Consequences Do These Facts Create When Writing In Rust?</h2>

<p>When writing a Rust program that you intend to distribute as a WebAssembly module, <code class="language-plaintext highlighter-rouge">cargo</code> knows that memory growth might be required; therefore, it builds the necessary functions into the WebAssembly module for calling <code class="language-plaintext highlighter-rouge">memory.grow</code>.
Should it be necessary, memory growth will now happen automatically (and silently!)</p>

<p>The consequences for JavaScript are that its shared memory <code class="language-plaintext highlighter-rouge">ArrayBuffer</code> now points to a completely new block of memory and all the overlay objects that gave access to the “pre-growth” shared memory are no longer usable (I.E. they are said to have become “detached”).</p>

<p>If you then attempt to access shared memory using one of these “pre-growth” objects, you will see an error such as this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>TypeError: Cannot perform %TypedArray%.prototype.slice on a detached ArrayBuffer
</code></pre></div></div>

<blockquote>
  <h3 id="aside">Aside</h3>

  <p>Before you can compile a Rust program to WebAssembly, you must first install the <code class="language-plaintext highlighter-rouge">wasm32</code> compilation target:</p>

  <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rustup target add wasm32-unknown-unknown
</code></pre></div>  </div>
</blockquote>

<h2 id="local-execution">Local Execution</h2>

<p>The following trivial application demonstrates this problem.</p>

<p>A WebAssembly program shares a block of memory with its host for the pourposes of data exchange.
The host writes data to known locations in memory, then the WebAssembly program processes it and writes its response back at another known location.</p>

<blockquote>
  <h3 id="source-code">Source Code</h3>

  <p>All the source code referenced by this blog can be found in the Github repository <a href="https://github.com/ChrisWhealy/detached_arraybuffer"><code class="language-plaintext highlighter-rouge">detached_arraybuffer</code></a>.</p>

  <p>If you wish to run these tests locally, first clone this repo:</p>

  <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone git@github.com:ChrisWhealy/detached_arraybuffer.git
</code></pre></div>  </div>
</blockquote>

<h3 id="first-generate-the-webassembly-module">First, Generate the WebAssembly Module</h3>

<p>Testing can be performed using different versions of the Wasm module.  One version will work because it does not perform memory growth, and the other will break because it does:</p>

<ol>
  <li>
    <p>Compile a <a href="https://github.com/ChrisWhealy/detached_arraybuffer/blob/master/memoryguest.wat">working version</a> from source code written in WebAssembly Text.</p>

    <p>This version works simply because the WebAssembly Text source code was hand-written, and no such calls to <code class="language-plaintext highlighter-rouge">memory.grow</code> were implemented.  To use this version, run</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>wat2wasm memoryguest.wat
</code></pre></div>    </div>
  </li>
  <li>
    <p>Compile a <a href="https://github.com/ChrisWhealy/detached_arraybuffer/blob/master/src/lib_growth.rs">broken version</a> from source code written in Rust</p>

    <p>To use this version:</p>

    <ul>
      <li>Rename <code class="language-plaintext highlighter-rouge">./src/lib_growth.rs</code> to <code class="language-plaintext highlighter-rouge">./src/lib.rs</code></li>
      <li>Run <code class="language-plaintext highlighter-rouge">cargo build --target=wasm32-unknown-unknown</code></li>
    </ul>
  </li>
  <li>
    <p>Compile a <a href="https://github.com/ChrisWhealy/detached_arraybuffer/blob/master/src/lib_no_growth.rs">working version</a> from source code written in Rust that explicitly avoids the need for memory growth</p>

    <p>To use this version:</p>

    <ul>
      <li>Rename <code class="language-plaintext highlighter-rouge">./src/lib_no_growth.rs</code> to <code class="language-plaintext highlighter-rouge">./src/lib.rs</code></li>
      <li>Run <code class="language-plaintext highlighter-rouge">cargo build --target=wasm32-unknown-unknown</code></li>
    </ul>
  </li>
</ol>

<h3 id="test-the-wasm-module-by-calling-it-from-javascript">Test The Wasm Module By Calling It From JavaScript</h3>

<p>The effects of WebAssembly memory growth on JavaScript’s shared memory <code class="language-plaintext highlighter-rouge">ArrayBuffer</code> can be demonstrated as follows:</p>

<ol>
  <li>In both <a href="https://github.com/ChrisWhealy/detached_arraybuffer/blob/master/server.js"><code class="language-plaintext highlighter-rouge">server.js</code></a> and <a href="https://github.com/ChrisWhealy/detached_arraybuffer/blob/master/client.js"><code class="language-plaintext highlighter-rouge">client.js</code></a>, ensure that the variable <code class="language-plaintext highlighter-rouge">wasmFilePath</code> points to the particular Wasm module you wish to test.</li>
  <li>
    <p>To test the Wasm module server side, run</p>

    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>node server.js
</code></pre></div>    </div>
  </li>
  <li>
    <p>To test the Wasm module in a browser:</p>

    <ul>
      <li>
        <p>Start a temporary Web Server</p>

        <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code> python3 <span class="nt">-m</span> http.server 8080
</code></pre></div>        </div>
      </li>
      <li>Point your browser to <a href="http://localhost:8080">http://localhost:8080</a></li>
      <li>Open the developer console</li>
    </ul>
  </li>
</ol>

<p>When the test succeeds, the console will display</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Ahoy there, Testy McTestface!
</code></pre></div></div>

<p>When the test fails, the console will show the Type Error shown above.</p>

<h2 id="implementation">Implementation</h2>

<p>The map of shared memory looks like this:</p>

<table>
  <thead>
    <tr>
      <th style="text-align: right">Offset</th>
      <th>Contains</th>
      <th>Offset returned by Wasm function</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: right">0</td>
      <td>Salutation</td>
      <td><code class="language-plaintext highlighter-rouge">get_salutation_ptr</code></td>
    </tr>
    <tr>
      <td style="text-align: right">16</td>
      <td>Name</td>
      <td><code class="language-plaintext highlighter-rouge">get_name_ptr</code></td>
    </tr>
    <tr>
      <td style="text-align: right">32</td>
      <td>Formatted greeting</td>
      <td><code class="language-plaintext highlighter-rouge">get_msg_ptr</code></td>
    </tr>
  </tbody>
</table>

<p>The JavaScript program must first obtain the values of the memory locations shown above.
Once it has these, it writes the appropriate strings to those locations.</p>

<p>Next, it calls the Wasm function <code class="language-plaintext highlighter-rouge">set_name</code> which does the following:</p>

<ul>
  <li>Combines the salutation and name into a greeting</li>
  <li>Writes that greeting to another known memory location</li>
  <li>Returns the length of the formatted greeting</li>
</ul>

<p>Finally, the JavaScript program reads the greeting from shared memory and writes it to the console.</p>

<h2 id="but-what-caused-memory-growth">But What Caused Memory Growth?</h2>

<p>Look at the Rust coding in <a href="https://github.com/ChrisWhealy/detached_arraybuffer/blob/master/src/lib_growth.rs">./src/lib_growth.rs</a>.
Within function <code class="language-plaintext highlighter-rouge">set_name</code>, the <code class="language-plaintext highlighter-rouge">format!()</code> macro is used to assemble the result, which is then stored in an intermediate <code class="language-plaintext highlighter-rouge">String</code> called <code class="language-plaintext highlighter-rouge">greeting</code>.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">#[no_mangle]</span>
<span class="k">pub</span> <span class="k">unsafe</span> <span class="k">extern</span> <span class="s">"C"</span> <span class="k">fn</span> <span class="nf">set_name</span><span class="p">(</span><span class="n">sal_len</span><span class="p">:</span> <span class="nb">i32</span><span class="p">,</span> <span class="n">name_len</span><span class="p">:</span> <span class="nb">i32</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">i32</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">sal</span><span class="p">:</span> <span class="o">&amp;</span><span class="nb">str</span> <span class="o">=</span> <span class="nf">str_from_buffer</span><span class="p">(</span><span class="n">SALUT_OFFSET</span><span class="p">,</span> <span class="n">sal_len</span> <span class="k">as</span> <span class="nb">usize</span><span class="p">);</span>
    <span class="k">let</span> <span class="n">name</span><span class="p">:</span> <span class="o">&amp;</span><span class="nb">str</span> <span class="o">=</span> <span class="nf">str_from_buffer</span><span class="p">(</span><span class="n">NAME_OFFSET</span><span class="p">,</span> <span class="n">name_len</span> <span class="k">as</span> <span class="nb">usize</span><span class="p">);</span>

    <span class="k">let</span> <span class="n">greeting</span><span class="p">:</span> <span class="nb">String</span> <span class="o">=</span> <span class="nd">format!</span><span class="p">(</span><span class="s">"{}, {}!"</span><span class="p">,</span> <span class="n">sal</span><span class="p">,</span> <span class="n">name</span><span class="p">);</span>
<span class="c1">// snip...</span>
</code></pre></div></div>

<p>Well that looks harmless enough…</p>

<p>However, the declaration of the new <code class="language-plaintext highlighter-rouge">String</code> requires more memory than is currently available; so, using the extra functions generated by <code class="language-plaintext highlighter-rouge">cargo</code>, shared memory is automatically and silently extended.</p>

<p>As far as Rust (WebAssembly) is concerned, everything is fine; however, the JavaScript host environment sees that shared memory has changed size, so it throws away the old <code class="language-plaintext highlighter-rouge">ArrayBuffer</code> and helpfully creates you a new one.</p>

<p>And now all your “pre-growth” JavaScript references into WebAssembly’s shared memory are broken…</p>

<h2 id="calling-the-broken-code-from-javascript">Calling The Broken Code From JavaScript</h2>

<p>Look at <a href="https://github.com/ChrisWhealy/detached_arraybuffer/blob/master/server.js">./server.js</a> to see the full context of this coding.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">salutation</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">Ahoy there</span><span class="dl">"</span>
<span class="kd">const</span> <span class="nx">name</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">Testy McTestface</span><span class="dl">"</span>

<span class="c1">// Treat shared memory as an array of unsigned bytes</span>
<span class="kd">const</span> <span class="nx">mem8</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Uint8Array</span><span class="p">(</span><span class="nx">wasmExports</span><span class="p">.</span><span class="nx">memory</span><span class="p">.</span><span class="nx">buffer</span><span class="p">)</span>

<span class="c1">// Fetch long-lived pointers</span>
<span class="kd">const</span> <span class="nx">sal_ptr</span> <span class="o">=</span> <span class="nx">wasmExports</span><span class="p">.</span><span class="nf">get_salutation_ptr</span><span class="p">()</span>
<span class="kd">const</span> <span class="nx">name_ptr</span> <span class="o">=</span> <span class="nx">wasmExports</span><span class="p">.</span><span class="nf">get_name_ptr</span><span class="p">()</span>
<span class="kd">const</span> <span class="nx">msg_ptr</span> <span class="o">=</span> <span class="nx">wasmExports</span><span class="p">.</span><span class="nf">get_msg_ptr</span><span class="p">()</span>

<span class="c1">// Store salutation and name at the expected locations</span>
<span class="nx">mem8</span><span class="p">.</span><span class="nf">set</span><span class="p">(</span><span class="nf">stringToAsciiArray</span><span class="p">(</span><span class="nx">salutation</span><span class="p">),</span> <span class="nx">sal_ptr</span><span class="p">)</span>
<span class="nx">mem8</span><span class="p">.</span><span class="nf">set</span><span class="p">(</span><span class="nf">stringToAsciiArray</span><span class="p">(</span><span class="nx">name</span><span class="p">),</span> <span class="nx">name_ptr</span><span class="p">)</span>

<span class="c1">// Tell Wasm to write the formatted greeting to the known memory location then return its length</span>
<span class="kd">let</span> <span class="nx">msg_len</span> <span class="o">=</span> <span class="nx">wasmExports</span><span class="p">.</span><span class="nf">set_name</span><span class="p">(</span><span class="nx">salutation</span><span class="p">.</span><span class="nx">length</span><span class="p">,</span> <span class="nx">name</span><span class="p">.</span><span class="nx">length</span><span class="p">)</span>

<span class="c1">// Read greeting from shared memory</span>
<span class="kd">let</span> <span class="nx">msg_text</span> <span class="o">=</span> <span class="nf">asciiArrayToString</span><span class="p">(</span><span class="nx">mem8</span><span class="p">.</span><span class="nf">slice</span><span class="p">(</span><span class="nx">msg_ptr</span><span class="p">,</span> <span class="nx">msg_ptr</span> <span class="o">+</span> <span class="nx">msg_len</span><span class="p">))</span>
<span class="c1">//                                ^^^^^^^^^^ mem8 will point to nothing if memory growth occurs!</span>

<span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="nx">msg_text</span><span class="p">)</span>
</code></pre></div></div>

<p>So let’s run this.</p>

<p>If you’re using the working WebAssembly module, you’ll see:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>node server.js
Ahoy there, Testy McTestface!
</code></pre></div></div>

<p>and if you’re using the WebAssembly module that breaks JavaScript’s shared memory references, you’ll see:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>node server.js
/Users/chris/Developer/WebAssembly/detached_arraybuffer/server.js:60
    <span class="nb">let </span>msg_text <span class="o">=</span> asciiArrayToString<span class="o">(</span>mem8.slice<span class="o">(</span>msg_ptr, msg_ptr + msg_len<span class="o">))</span>
                                           ^

TypeError: Cannot perform %TypedArray%.prototype.slice on a detached ArrayBuffer
    at Uint8Array.slice <span class="o">(</span>&lt;anonymous&gt;<span class="o">)</span>
    at /Users/chris/Developer/WebAssembly/detached_arraybuffer/server.js:60:44
</code></pre></div></div>

<h1 id="two-solutions">Two Solutions</h1>

<p>Until JavaScript’s <code class="language-plaintext highlighter-rouge">ArrayBuffer</code> is able to perform in-place growth, we must adopt one of two possible approaches to solving this problem.
Either:</p>

<ol>
  <li>We monitor the size of the WebAssembly memory looking for growth; or</li>
  <li>We adjust the Rust coding so that memory growth does not occur.</li>
</ol>

<h2 id="1-a-javascript-workaround">1. A JavaScript Workaround</h2>

<p>If it’s going to change, WebAssembly memory will only every increase in size.
So a simple way to workaround this problem is to monitor the size of the WebAssembly’s memory.</p>

<p>If it gets bigger, then you know you need to redefine any shared memory overlay objects.</p>

<blockquote>
  <p>This is just a workaround; it does not change the underlying problem.</p>

  <p>Anyone else calling the same WebAssembly function will need to implement the same workaround.</p>
</blockquote>

<p>The code does not require much modification to avoid using a possibly detached <code class="language-plaintext highlighter-rouge">ArrayBuffer</code>:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">salutation</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">Ahoy there</span><span class="dl">"</span>
<span class="kd">const</span> <span class="nx">name</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">Testy McTestface</span><span class="dl">"</span>

<span class="c1">// Keep track of Wasm's shared memory size</span>
<span class="kd">let</span> <span class="nx">memLength</span> <span class="o">=</span> <span class="nx">wasmExports</span><span class="p">.</span><span class="nx">memory</span><span class="p">.</span><span class="nx">buffer</span><span class="p">.</span><span class="nx">byteLength</span>

<span class="c1">// Snip</span>

<span class="c1">// Tell Wasm to write the formatted greeting to the known memory location then return its length</span>
<span class="kd">let</span> <span class="nx">msg_len</span> <span class="o">=</span> <span class="nx">wasmExports</span><span class="p">.</span><span class="nf">set_name</span><span class="p">(</span><span class="nx">salutation</span><span class="p">.</span><span class="nx">length</span><span class="p">,</span> <span class="nx">name</span><span class="p">.</span><span class="nx">length</span><span class="p">)</span>

<span class="c1">// Before allowing shared memory access, check if memory growth has occurred</span>
<span class="k">if </span><span class="p">(</span><span class="nx">wasmExports</span><span class="p">.</span><span class="nx">memory</span><span class="p">.</span><span class="nx">buffer</span><span class="p">.</span><span class="nx">byteLength</span> <span class="o">&gt;</span> <span class="nx">memLength</span><span class="p">)</span> <span class="p">{</span>
  <span class="nx">memLength</span> <span class="o">=</span> <span class="nx">wasmExports</span><span class="p">.</span><span class="nx">memory</span><span class="p">.</span><span class="nx">buffer</span><span class="p">.</span><span class="nx">byteLength</span>
  <span class="nx">mem8</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Uint8Array</span><span class="p">(</span><span class="nx">wasmExports</span><span class="p">.</span><span class="nx">memory</span><span class="p">.</span><span class="nx">buffer</span><span class="p">)</span>
<span class="p">}</span>

<span class="c1">// Read greeting from shared memory</span>
<span class="kd">let</span> <span class="nx">msg_text</span> <span class="o">=</span> <span class="nf">asciiArrayToString</span><span class="p">(</span><span class="nx">mem8</span><span class="p">.</span><span class="nf">slice</span><span class="p">(</span><span class="nx">msg_ptr</span><span class="p">,</span> <span class="nx">msg_ptr</span> <span class="o">+</span> <span class="nx">msg_len</span><span class="p">))</span>

<span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="nx">msg_text</span><span class="p">)</span>
</code></pre></div></div>

<p>Now everything works because we’re on the lookout for memory growth and then “reattach” the <code class="language-plaintext highlighter-rouge">mem8</code> array to the new shared memory <code class="language-plaintext highlighter-rouge">ArrayBuffer</code>.</p>

<h2 id="2-solve-the-problem-in-rust">2. Solve the Problem in Rust</h2>

<p>However, to avoid causing inadvertent memory growth, the Rust coding needs to avoid invoking any instructions that might require extra memory.
In this case, it means that instead of using an intermediate <code class="language-plaintext highlighter-rouge">String</code> object, we write the bytes of the character strings directly to the <code class="language-plaintext highlighter-rouge">[u8]</code> buffer.</p>

<p>The full solution can be seen in <a href="https://github.com/ChrisWhealy/detached_arraybuffer/blob/master/src/lib_no_growth.rs">./src/lib_no_growth.rs</a>, but the important change is shown below:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">pub</span> <span class="k">unsafe</span> <span class="k">extern</span> <span class="s">"C"</span> <span class="k">fn</span> <span class="nf">set_name</span><span class="p">(</span><span class="n">sal_len</span><span class="p">:</span> <span class="nb">i32</span><span class="p">,</span> <span class="n">name_len</span><span class="p">:</span> <span class="nb">i32</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">i32</span> <span class="p">{</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">idx</span><span class="p">:</span> <span class="nb">usize</span><span class="p">;</span>

    <span class="c1">// Write salutation directly to the buffer</span>
    <span class="nf">copy_bytes</span><span class="p">(</span><span class="n">MSG_OFFSET</span><span class="p">,</span> <span class="n">SALUT_OFFSET</span><span class="p">,</span> <span class="n">sal_len</span><span class="p">);</span>
    <span class="n">idx</span> <span class="o">=</span> <span class="n">MSG_OFFSET</span> <span class="o">+</span> <span class="n">sal_len</span> <span class="k">as</span> <span class="nb">usize</span><span class="p">;</span>

    <span class="c1">// Write separator ", "</span>
    <span class="n">BUFFER</span><span class="p">[</span><span class="n">idx</span><span class="p">]</span> <span class="o">=</span> <span class="n">COMMA</span><span class="p">;</span>
    <span class="n">idx</span> <span class="o">+=</span> <span class="mi">1</span><span class="p">;</span>
    <span class="n">BUFFER</span><span class="p">[</span><span class="n">idx</span><span class="p">]</span> <span class="o">=</span> <span class="n">SPACE</span><span class="p">;</span>
    <span class="n">idx</span> <span class="o">+=</span> <span class="mi">1</span><span class="p">;</span>

    <span class="c1">// Write name</span>
    <span class="nf">copy_bytes</span><span class="p">(</span><span class="n">idx</span><span class="p">,</span> <span class="n">NAME_OFFSET</span><span class="p">,</span> <span class="n">name_len</span><span class="p">);</span>
    <span class="n">idx</span> <span class="o">+=</span> <span class="n">name_len</span> <span class="k">as</span> <span class="nb">usize</span><span class="p">;</span>

    <span class="c1">// Write bang character</span>
    <span class="n">BUFFER</span><span class="p">[</span><span class="n">idx</span><span class="p">]</span> <span class="o">=</span> <span class="n">BANG</span><span class="p">;</span>
    <span class="n">idx</span> <span class="o">+=</span> <span class="mi">1</span><span class="p">;</span>

    <span class="p">(</span><span class="n">idx</span> <span class="o">-</span> <span class="n">MSG_OFFSET</span><span class="p">)</span> <span class="k">as</span> <span class="nb">i32</span>
<span class="p">}</span>
</code></pre></div></div>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1">
      <p>memory growth could be invoked either from WebAssembly or the host environment <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Chris Whealy</name></author><category term="chriswhealy" /><summary type="html"><![CDATA[When JavaScript acts as the host environment for WebAssembly, shared memory is visible to JavaScript as an ArrayBuffer. WebAssembly memory is allowed to grow, but JavaScript ArrayBuffers are not; so what happens when your Rust program (compiled to WebAssembly) asks for more memory?]]></summary></entry><entry><title type="html">Human-centered Code Reviews</title><link href="https://awesome.red-badger.com//niall-rb/human-centered-code-reviews" rel="alternate" type="text/html" title="Human-centered Code Reviews" /><published>2022-06-22T12:00:00+00:00</published><updated>2022-06-22T12:00:00+00:00</updated><id>https://awesome.red-badger.com//niall-rb/human-centered-code-reviews</id><content type="html" xml:base="https://awesome.red-badger.com//niall-rb/human-centered-code-reviews"><![CDATA[<p>For the uninitiated a code review is basically when a developer presents their code to another for comments and feedback. If the code passes the review it is typically merged or if it doesn’t then the submitter has some changes to make before submitting it for review again. They can come at the end when the feature is complete or more commonly will have multiple reviews along the way.</p>

<p>In all but the smallest of teams code reviews are a commonplace practice. The primary purpose of them is to ensure that the code is of good quality and meets the team’s technical standards of fitness.</p>

<p>Some of the things a reviewer could look for could include:</p>
<ul>
  <li>Proper grammar and syntax</li>
  <li>Efficient algorithms</li>
  <li>No duplicate code</li>
  <li>Adherence to naming conventions</li>
  <li>Test coverage</li>
</ul>

<p>… and so forth.</p>

<p>I think everything I’ve described above is pretty uncontroversial yet In spite of all of this I think code reviews today are (with some justification) a somewhat maligned practice.</p>

<p>Code reviews and their efficacy rely heavily on the existing relationships between the team members. Unhappiness with technical direction and personal animosities can spill over into the review process and end up harming team performance.</p>

<p>Conversely, teams that get along and have a solid and agreed vision of what good code looks like never seem to suffer these pitfalls and the process is a routine and effective guarantor of code quality.</p>

<p>To explore some of the complexities around code reviews we first need to examine its origins and recall how software used to be shipped.</p>

<h2 id="how-things-were">How things were</h2>
<p>In the past releasing new software was a chore. Preparing the release candidate, notifying internal stakeholders and users about it, weeks of manual regression testing and warnings of downtime on the day of release were all part and parcel of the process. Fear of what could go wrong loomed large over the whole process.</p>

<p>Since the cost of change was so high, <em>getting things right the first time</em> was of cardinal importance. A botched deployment meant kicking off the same lengthy process all over again to revert the failed change.</p>

<p>Developers were constrained by the tools and methodologies of the time - being totally confident in the success of a deployment was near impossible. Test driven development, a now widely accepted practice for writing resilient code wasn’t developed till the late 90’s by Kent Beck yet code was already written for devices large and small long before this.</p>

<p>In these times code reviews were a vital tool to ensure code quality. Seasoned engineers would pour over the proposed changes looking for syntax errors, potential compilation issues or any other gaps in code quality that could derail a release and result in a bunch of upset customers or users. It was manual, laborious and the stakes were high if something slipped through.</p>

<h2 id="how-things-are-now">How things are now</h2>
<p>The tools and methodologies used to ensure quality code have gone from sparse to abundant.</p>

<p>Developers have their choice of rich IDE’s that offer access to a vast plugin ecosystem for most any active programming language, providing convenience features like syntax highlighting and code formatting and more advanced features like code completion.</p>

<p>Testing frameworks are faster and more feature rich. With a solid testing strategy, developers can have a high degree of confidence that their new code has the intended effect without new bugs or regressions cropping up.</p>

<p>Today’s devs can even provision production-like services on their development machines through techniques like containerisation, providing lean standalone services - the kind of thing that would have been unimaginable for complicated subsystem teams of the past.</p>

<p>For companies that have leaned into continuous delivery and continuous integration as practices, code deployments that once took hours or days could be achieved in mere minutes. Tools like Github Actions can run automated checks against our bundled software before deploying it to production. Some of these checks can include automated tests (unit, e2e, contract etc..), static analysis tools, vulnerability scans just to name a few.</p>

<p>So what does this all mean?</p>

<p>The code review has gone from being one of a handful of practices to ensure code quality to just one amongst many. Verifying that our code can be safely merged is now also possible through another rival practice - pair programming.</p>

<h2 id="shifting-left">Shifting left</h2>
<p>The traditional way in engineering teams would solve coding problems was pretty straight forward - analyse the problem, write code to fix it then test the code. This is an approach which fits neatly into the roles of business analyst, developer and QA. Actual testing only came at the end.</p>

<p>The problem with this approach was that oftentimes requirements were poorly understood or missed entirely and too often devs would end up building the wrong thing. This left QA’s in an unenviable position of either sending time sensitive work back to developers to fix, potentially risking a deliverable, or letting it slide and creating a separate bug to track whatever got missed.</p>

<p>In 2001 Larry Smith proposed a different approach. He coined the term “shift left testing” to describe testing in smaller iterations. Instead of writing code and only testing it once it was done, he advocated for testing during development, in smaller iterations, so together the dev and QA could both be more confident that what’s being built would match what was asked for.</p>

<p>Around the same time, the Extreme Programming (XP) philosophy was gaining popularity. It challenged teams to take ownership of the software they ship, and to not view their role in the team as being siloed just to their job title. Far from just being on the shoulders of QAs and testers, ensuring the quality of the final product was becoming a whole team concern.</p>

<p>Baking in quality from the start would prove to be a powerful idiom and expanded to other areas of software delivery, particularly around non functional requirements. Testing, accessibility, observability, security - all of these that were traditionally regarded as something that was considered “once development was done” but are now seen as critical requirements that have to be considered up front.</p>

<p>Pair programming was conceived in the same spirit.</p>

<p>Having two developers working at the same terminal has existed for quite a while but true pairing - where the design, problem analysis and writing of code is conducted by peers that discuss approaches and alternate roles - would take time to grow in acceptance.</p>

<p>While controversial to some, pairing represents a shift left in the approval process.</p>

<p>Instead of having discrete write-code, review-code blocks - the two phases are merged into a single process where code is co-authored by two devs. This satisfies the “two pairs of eyes” principle and so any code they write could go to production - which is why it’s often used in conjunction with trunk based development.</p>

<p>In spite of the difficulty introducing pairing and trunk based development to engineering teams that haven’t used them before the benefits can be huge - with automated testing and CI/CD pipelines, teams can ship quality code many times a day with high confidence and none of the waiting around and context switching that characterises the humble code review.</p>

<h2 id="whats-wrong-with-code-reviews">What’s wrong with code reviews?</h2>
<p>Before exploring how we can do code reviews better we need some appreciation of what are the practical drawbacks using code reviews as a means of safeguarding code quality in the first place.</p>

<p>I’ll group my critique of code reviews into two broad categories:</p>
<ul>
  <li>The process itself</li>
  <li>Developer attitudes towards them</li>
</ul>

<h3 id="code-reviews-as-a-process">Code reviews as a process</h3>
<p>Utilising code reviews means developer workflows are habitually broken.</p>

<p>If you’re writing code then you need to put out a request for a review and await the outcome of it before resuming work. If you’re the reviewer then your work is potentially being interrupted by being asked to conduct a review. These shifting and exchanging responsibilities are a form of context switching - dropping what you were doing before and focusing on something else.</p>

<p>Proponents of code reviews would argue the original submitter could just do something else while the review is ongoing but this too isn’t ideal. Having devs split their attention between multiple streams of work is inefficient and risks the quality of one or both being compromised.</p>

<p>Code reviews also contain within them an innate antagonism, forcing teams to choose between one undesirable consequence and another - and that is what is the ideal size of a pull request?</p>

<p>Small commits are generally considered to be good practice in coding. They encourage developers to be deliberate in their choices and result in more focused changes. Having pull requests with fewer commits mean they reveal their intention more clearly too, which makes reviewing them far more straightforward.</p>

<p>However, pull requests consisting of only one or two commits don’t gel very well with the manual nature of code reviews. If I produce multiple pull requests in a day then I’ll require a lot of reviews, which means lots of context switching for those on my team who have to review my work (and maybe some desensitisation towards my repeated entreaties for a review too)</p>

<p>But what about batching together many commits so instead of many small ones you just have one big one? You can do this but it has a cost attached.</p>

<p>Blending many commits into a single pull request means that the surface area of change is much greater, leading to a proportionally higher review time. While good test names and commit messages can mitigate this, the overall goal of what’s being achieved can become blurred, mandating a slower line-by-line approach to reviewing code to see what’s changed and where. Overly stuffed pull requests do not clearly reveal their intention like their smaller counterparts.</p>

<p>Or even worse if the surface area of change is massive then something could just slip by entirely e.g. renaming a file while also making business logic changes that could be lost in the revision comparison tool. Being “too big to review” is a risk too.</p>

<p>Ultimately teams need to come to their own conclusions about how big or small code reviews should be but either way there’s a cost involved regardless.</p>

<h3 id="developer-attitudes-to-code-reviews">Developer attitudes to code reviews</h3>
<p>Prevailing attitudes within engineering teams towards the review process itself can help or hinder the process as a whole. We understand that code reviews are primarily a technical assessment to assess the fitness of some code though I would argue it has a psychosocial facet to it too.</p>

<p>What does good code look like? Who is in a position to judge good from bad? What technologies or technical practices are desirable or not?</p>

<p>These are all subjective questions that don’t have hard answers. In the absence of some socialised view of what “good” looks like, developers will bring their experience to bear on the review - they also bring their biases, subjectivities and sometimes their egos too.</p>

<p>Commercial software development is a time consuming and expensive process. Meeting milestones on product roadmaps can take a long time to hit and forecasting can be like crystal ball gazing at best of times. For businesses continuous feature delivery is incredibly important, if not essential to their survival.</p>

<p>However this urgency is not always reflected in the spirit with which code is reviewed. Spurious or ineffectual review comments go beyond simply being annoying and if unchecked can be harmful and erode team trust.</p>

<h2 id="is-there-a-better-way">Is there a better way?</h2>
<p>Despite all these drawbacks I do think code reviews can be made to work.</p>

<p>However I think doing them effectively means engineering teams re-evaluating governance of the review process. Useful new tools can also help as previous tedious manual checks and be neatly automated away.</p>

<p>We also need to evaluate the human impact of the review process so we’ll touch on how giving effective feedback can guide us to a better, more empathetic review process.</p>

<h2 id="team-owned-quality">Team owned quality</h2>
<p>Some of the drawbacks of code reviews can seem quite apparent but others are much more subtle. One such subtlety is who owns code quality? The natural answer seems to be the senior most developers and technical leaders with the engineering teams. Their experience makes them natural arbiters of code quality and so it’s natural to assume that they should play an oversized role in making sure the code is up to scratch.</p>

<p>While this all seems quite reasonable I think this is a backward and self-defeating approach.</p>

<p>It’s somewhat uncommon for engineering teams to be staffed entirely with experienced developers. The commercial demands placed on organisations mean that there is always more work to be done than developers to do the work.</p>

<p>For this reason we see the emergence of leveraged teams - that is to say teams with one or two highly experienced developers, some with moderate experience and then some more junior developers to round out the team.</p>

<p>I would propose that having code quality owned by a handful of experienced developers is harmful to both the experienced devs and the mid/junior developers they are seeing to grow and teach:</p>

<p>Tech leads and principal developers often have responsibilities that lie outside the immediate concerns of the delivery team. Some include: delivery assurance discussions, product discovery, line management and cultivation concerns, tech analysis sessions, engineering working groups, tech visioning discussions and so forth. In short, they have a lot on their plate outside of coding (if indeed they have time to code at all).</p>

<p>On top of all this, if they alone are required to approve the code their team generates then it’s easy for them to become a single point of failure. Code reviews take time to effectively review and delay is expensive. Equally as someone that isn’t writing much code they might actually have less context on the codebase should be doing than other engineers that do nothing but write code all day.</p>

<p>This format is harmful for less experienced developers too. Being unable to influence coding standards risks them becoming disengaged and seeing quality as something that is the responsibility of QA’s and senior developers, not themselves. Why care about standards you have no agency to change? This is also harmful to their growth and could prompt talented developers to actively look for opportunities somewhere else where they can have more autonomy and influence.</p>

<p>The solution to these problems is team owned quality.</p>

<p>Engineering teams should get together early after forming and talk about coding standards in a collaborative and inclusive way. This should continue for the life of the team. The meeting should be a discussion where everyone has a chance to contribute and debate what makes for good code.</p>

<p>Senior developers should help guide the conversation but not act as gatekeepers of what is good and what isn’t and should approach discussions with an open mind. Though junior developers are new to their career, they can produce useful insights and fresh perspectives.</p>

<p>These sessions should be repeated weekly or fortnightly, with discussion points brought in eg. “can we adopt this new technology?”, “does this approach seem off to anyone else”,  “Is it just me or are our pipeline tests flaky?”. As with all well run meetings, notes should be taken and actions assigned to individuals to follow up on before the next meeting where they recap their progress.</p>

<p>The offshoot of this is that everyone has an equal responsibility to ensure the quality of the software the team writes. If indeed a bug is released, it can be included as a discussion point in the next meeting (in a candid but blame-free way) and the team can come up with actions on how to prevent a similar event from happening in the future.</p>

<p>This approach to communally owned coding standards means anyone in the team can be an approver. This frees up experienced developers from being gatekeepers of quality while giving more challenging and engaging opportunities for growth to less experienced developers as they’re forced to contemplate broader sets of criteria their code must adhere to beyond simply “does it work?”</p>

<h2 id="automate-where-possible">Automate where possible</h2>
<p>When approaching a code review there is usually some kind of coding standard to which the team adheres to. Critically assessing code can be time consuming and laborious so any way we can reduce cognitive load should be explored.</p>

<p>Simply put, if we can automate part of the review process effectively then we should. This could include things like maximum line length, indentation size, spaces after function name etc… Abstracting away these minor aspects of code style means reviewers can focus their attention on higher level concerns.</p>

<p>This can be accomplished using linters like eslint. Code formatters can be implemented as a pre-commit hook or even integrated with the developers IDE so that when they save a file the code is automatically formatted - this means that we can have linter config files as part of the codebase so that the same formatting rules are always applied regardless of who has checked out the code.</p>

<p>Automation isn’t just for linting though, we can use static analysis tools to catch missing code coverage or any security vulnerabilities that our changes to the project dependencies might miss out on.</p>

<p>Automation is one of the simplest ways to bake quality into our development process for a relatively modest initial investment.</p>

<h2 id="empathetic-feedback">Empathetic feedback</h2>
<p>The process of giving feedback, let alone technical feedback is at times poorly understood.</p>

<p>When giving feedback of any kind there are a couple of things to keep in mind:</p>
<ul>
  <li>Am I acknowledging the subjectivity of my feedback? Eg. making conclusions without room for disagreement</li>
  <li>Have I left my own biases and preferences at the door?</li>
  <li>Have I thought about how the person who asked might receive my feedback?</li>
  <li>Are my critiques actionable?</li>
</ul>

<p>This stuff all seems basic but too often it’s missing completely. Ineffective developers may seize on code reviews as a chance to exert undue influence over their peers, requiring changes that have no material impact on the outcome.</p>

<p>The best developers use code reviews as teachable moments, where the problem domain is explored in partnership with the person who asked for the review, if indeed something needs to be fixed. The experience or title gap between them is immaterial as they both explore what solution fits best. One-to-one interaction is preferred over review comments where some of the nuance of the conversation may be lost.</p>

<p>Finally, savvy reviewers will also understand that not all code quality issues need to be addressed at the code review stage. If something is unusual and perhaps a little contentious, then it can be addressed asynchronously the next time devs get together to talk about code. Good devs appreciate the cost involved in delay and will never seek to hold up code from being merged any more than strictly necessary.</p>

<p>This neatly leads us to the final point in how we can have more effective code reviews.</p>

<h2 id="technical-feedback">Technical feedback</h2>
<p>One of the most common memes when it comes to developers is that it’s hard to get a straight answer from them. Every decision seems to prompt a lot of discussions about trade offs and opportunity costs.</p>

<p>Unfortunately I think there’s an element of truth to this. Most decisions, especially technical ones, are rarely straightforward and clear cut and usually there is an element of compromise to them. In spite of this ambiguity I think there is some general guidance we can apply when reviewing code for frequently deployed systems that have a low cost of change.</p>

<p>Let’s look at some valid justifications for rejecting a pull request and asking the dev that submitted it to look at it again (for brevity we’re going to assume the project builds successfully and unit tests are passing)</p>

<h3 id="it-doesnt-adhere-to-the-teams-coding-standards">It doesn’t adhere to the teams coding standards</h3>
<p>This is pretty straightforward. I won’t go into detail as we’ve already covered this but a team should agree what good looks like and agree to follow those standards. This should be the least contentious type of review comment as everyone had a chance to give their input in shaping these standards.</p>

<h3 id="the-code-has-quantitative-deficiencies">The code has quantitative deficiencies</h3>
<p>This is where code quality is compromised in ways that, while not explicitly caught by coding standards, have a clear and identifiable negative impact.</p>

<p>For example, say a dev introduces a new dependency to the project to perform some functionality that is already present in language used. In the Javascript programming language (and others) concise dependency trees lessens potential version conflicts when upgrading. It also creates a smaller vector of attack as fewer dependencies means fewer packages that could, in the course of time, become outdated and insecure.</p>

<p>In this instance, rejecting this code change upholds a facet of code quality that can’t be strongly argued against, especially since the introduced functionality is already present within our chosen language.</p>

<p>Unjustified code duplication, using deprecated language features or indeed any change that meaningfully compromises the non functional requirements of our system are all examples of quantitative deficiencies.</p>

<p>Beyond these two cases I think review comments fall into the realm of subjectivity. Responsible reviewers can and should acknowledge this and frame their critiques accordingly. Teams that can compromise where appropriate show maturity, even if there are some minor disagreements initially.</p>

<h2 id="conclusion">Conclusion:</h2>
<p>Doing code reviews well requires trust, openness and empathy within the teams that practice them.</p>

<p>Far from being a mundane technical practice, code reviews have the chance to energise and engage software delivery teams by fully placing ownship of the software they write in their hands.</p>

<p>So let’s democratise code quality and build a vision of how our system could work that includes everyone.</p>]]></content><author><name>Niall Bambury</name></author><category term="niall-rb" /><summary type="html"><![CDATA[An examination of the practices, values and ways of working that underpin effective code reviews]]></summary></entry><entry><title type="html">In defence of the testing pyramid</title><link href="https://awesome.red-badger.com//charypar/in-defence-of-the-testing-pyramid" rel="alternate" type="text/html" title="In defence of the testing pyramid" /><published>2022-06-13T20:00:00+00:00</published><updated>2022-06-13T20:00:00+00:00</updated><id>https://awesome.red-badger.com//charypar/in-defence-of-the-testing-pyramid</id><content type="html" xml:base="https://awesome.red-badger.com//charypar/in-defence-of-the-testing-pyramid"><![CDATA[<p>I like simplified models to guide decisions on how to build systems. There’s enough complexity in any software project, that without some guiding principles that at least seem theoretically correct, we’re likely to make suboptimal choices based on a coin toss. Simplified models neatly encapsulate the experience from previous attempts at doing something and help inform the future attempts.</p>

<p>So long as they are not too simplified. To actually work, the models need to be clear, reasonably complete, and actionable. If I have a concrete question, even as simple as a decision between two alternative approaches, the model needs to give me clear guidance on how to proceed. Case in point, the testing pyramid:</p>

<p><img src="https://miro.medium.com/max/1400/1*Tcj3OsK8Kou7tCMQgeeCuw.png" alt="The testig pyramid" />
<em>Image source: <a href="https://betterprogramming.pub/the-test-pyramid-80d77535573">https://betterprogramming.pub/the-test-pyramid-80d77535573</a></em></p>

<p>We’ve all seen this picture. It is a seemingly helpful guide: To minimise cost of testing and maximise reliability and resulting quality, put more effort into, and rely more on tests closer to the implementation than the tests that are covering a wider scope. Seems intuitively correct, but fails my criteria: It’s unclear what integration tests and even unit tests actually are, specifically. It also doesn’t help answer questions like “Should I run end to end tests on every pull request?”. And where does static analysis fit in? – it’s unclear, incomplete, and it isn’t actionable.</p>

<p>As a consequence, people don’t seem to find it particularly helpful, and even propose various modifications which resonate with their experience better, like the <a href="https://kentcdodds.com/blog/write-tests">testing trophy, proposed by Kent C. Dodds</a>. I think these variants come down to the misalignment about what the individual layers of the pyramid are, what value they bring, and how costly they are to execute. And I’ve seen one too many testing strategies in which the pyramid is completely upside down, but the engineering team just doesn’t know how to do their testing any better. The pyramid is supposed to guide them, alas it stays quiet.</p>

<p>I don’t think there’s anything wrong with the pyramid. It’s just not quite detailed enough to be useful. Good first approximation, but I think we can do better.</p>

<h2 id="some-assumptions">Some Assumptions</h2>

<p>To make a better version of the pyramid, I will need to make some assumptions about the kind of system we’re testing, and the high-level ways of working of the team, and roles involved in the building of the product. At a high level, I will assume that:</p>

<ul>
  <li>We’re building a distributed system, composed of independently deployed services and applications. Running the entire system requires an environment.</li>
  <li>Our system has external dependencies which we don’t control - services delivered by separate organisations or other, independent teams in our organisation</li>
  <li>We’re doing continuous delivery, even continuous deployment to production, and therefore heavily rely on a Continuous Integration (CI) service</li>
  <li>Our branching workflow resembles Github flow - we have a single main branch, into which contributions are made in the form of small pull requests which get reviewed before merging</li>
</ul>

<p>This feels like a pretty typical situation most digital product teams find themselves in. The situation is probably different for game developers, desktop software developers, machine learning engineers and data scientists, and others. So, as always, your mileage may vary.</p>

<h2 id="the-improved-pyramid">The improved pyramid</h2>

<p>With that out of the way, here’s a (hopefully) more complete and useful pyramid:</p>

<p><img src="/assets/charypar/testing-pyramid.png" alt="Revised testig pyramid" /></p>

<p>This one has five layers, which are defined by what gets tested, where, when, why and how. The layers still form a pyramid, because in order for the upper layers of the pyramid to pass (or even run), the lower layers have to have passed first. Upper layer tests require more setup and more infrastructure, which makes them much more expensive to run than lower layer ones, so the overall volume of testing on the upper layers needs to be lower to maintain a stable cost/benefit ratio across layers. Or, from a more outcome oriented perspective:</p>

<blockquote>
  <p>The goal of each layer is to more cheaply and more regularly gain a reasonably high confidence that the layer above will pass, so that it does not need to be run as often</p>
</blockquote>

<p>In other words, the lower layers are an optimisation of executing the upper layers, in order to reduce their cost while maintaining a high enough overall confidence in the system.</p>

<h3 id="changing-tests-with-implementation-is-fine-top-heavy-pyramids-are-not-fine">Changing tests with implementation is fine, top-heavy pyramids are not fine</h3>

<p>Some will argue that pushing tests lower, closer to the implementation will lead to us having to regularly change tests when we change implementation, and that the execution time saved will instead be spent on keeping the lower layer tests up to date. To that, I will say that the idea that it should be possible to change implementation without changing tests is just… nonsense.</p>

<p>If it were true, it would mean that everything can be exhaustively tested through the interfaces it is consumed through - through the UI or public API, or something very close to it. That thinking obviously completely ignores the “physics” of software, and all the reasons we are building systems in a decomposed, modular way, which encapsulates and reuses logic. The sheer number of test cases necessary to capture all the nuances of all our business logic all at once is just not practical. And it’s not difficult to show that.</p>

<p>Let’s say each logical “unit” of our software requires, on average, 10 scenarios to test it thoroughly. Then, if these units are in any way dependent on one another (either one uses the other, or it uses the resulting state the other created), the number of scenarios multiply, and there are a 100 scenarios to cover for just two units, a 1000 for three, … This clearly doesn’t scale even a little bit! Let alone if our system is a complex sequential user journey, like an onboarding flow or a checkout in an online shop. That’s why we decompose software into functions and modules in the first place.</p>

<p>No other discipline building even remotely complicated things approaches testing in that way. Nobody sane would argue that a car should be tested only through driving it, on an actual public road, because otherwise we’d need to change the tooling when we change components. Of course the engine is tested independently from the tyres and the chassis. Therefore, the only remaining question is - how big is a unit we will test. And the bigger it is, the more tests are needed to cover it. And the more tests will need to be written again, if the unit is changed substantially. This is an inevitable reality driven purely by the complexity of the system, and doesn’t mean we shouldn’t build instrumentation for its constituent parts, just because we might replace them.</p>

<h3 id="external-dependencies-are-costly">External dependencies are costly</h3>

<p>You might also argue that layers 2 and 3 seem like an artificial split. We should be able to test everything on layer 3, surely. And in an ideal world, we could. The problem with testing against non-production environments of external dependencies is purely practical:</p>

<ul>
  <li>They are not always available</li>
  <li>They are often slow or rate limited</li>
  <li>They lack test data or require creating it for every run</li>
  <li>They are stateful</li>
</ul>

<p>All these reasons make repeatable, reliable testing solely against external systems difficult. But fully relying on mocks is not a reliable strategy either. We need to be sure that the mocks still behave like the actual system. We need to do both.</p>

<h2 id="guiding-principles">Guiding principles</h2>

<p>So, we now have a more complete version of the pyramid. To make it an actionable model, we just need some overall guiding principles:</p>

<ol>
  <li>All layers of the pyramid are necessary in order to achieve a high confidence in your system.</li>
  <li>Always prefer testing functionality on lowest possible layer.</li>
  <li>When a layer of testing starts slowing you down, reduce the volume of that layer and replace it by coverage on the lower layers. Introduce new forms of testing on lower layers.</li>
  <li>Do not give in to the temptation of moving layers “left” in the development process, executing the more expensive ones earlier or more often.</li>
</ol>

<p>The last principle is worth talking about a little more. It suggests, for example, that running automated, system level, end to end test on each pull requests, as tempting as it sounds, is not worth the effort and complexity. End to end tests require an environment to run in (see assumptions), which would need to be created and destroyed (or allocated and cleaned up) for each pull request. In my experience, this is really complex, slow and highly unlikely to pay off in additional confidence gained, compared to running the end to end test(s) on the main branch, after merging the pull request.</p>

<p>With these principles, I hope the model is now actually useful - clear, complete and actionable. It should be possible to take every form of testing you can think of, decide what layer it belongs to, and therefore where and when it should execute.</p>

<p>Building up this pyramid will take time, and at first, expanding the higher layers will pay off much more than the lower layers. But these are diminishing returns. That’s why so many teams come up with an upside down pyramid that takes hours to run and still lets many bugs through. It’s so tempting to just add another end to end test. Don’t fall into that trap. Follow the above principles, and turn the pyramid the right way up.</p>

<h2 id="built-to-be-testable">Built to be testable</h2>

<p>I have one final observation about all this: If, despite your best effort, you can’t seem to push test coverage down the pyramid, you may need to revisit your architecture, or even technology choices. Testability entirely depends of how your system is built.</p>

<p>In a system without stubs of external dependencies, achieving reasonably high coverage with automated end to end tests will be difficult and slow. In a system with services sharing state (a database for example), pushing tests from layer 2 down to layer 1 will be difficult. In a codebase with no concept of dependency injection (even a really simple one), moving coverage from functional tests to unit tests will be difficult (and require extensive, messy mocking and stubbing). In a system without contract testing or shared API types ensuring client/server alignment on layer 0, a lot more layer 3 tests will be required to gain the same level of confidence. In a dynamic language with few static guarantees, layer 1 will almost certainly be much larger than layer 0.</p>

<p>I could keep going, but you see the point. Fast reliable testing begins with smart engineering. Hopefully this more specific pyramid will help you make smarter choices.</p>]]></content><author><name>Viktor Charypar</name></author><category term="charypar" /><summary type="html"><![CDATA[I don’t think there’s anything wrong with the testing pyramid. It’s just not quite detailed enough to be useful. Good first approximation, but we can do better.]]></summary></entry><entry><title type="html">Creating a Miro Clone in the browser</title><link href="https://awesome.red-badger.com//ceddlyburge/visual-sort" rel="alternate" type="text/html" title="Creating a Miro Clone in the browser" /><published>2022-05-20T12:00:00+00:00</published><updated>2022-05-20T12:00:00+00:00</updated><id>https://awesome.red-badger.com//ceddlyburge/visual-sort</id><content type="html" xml:base="https://awesome.red-badger.com//ceddlyburge/visual-sort"><![CDATA[<h2 id="introduction">Introduction</h2>

<p>We recently worked with a client who asked us to create a freestyle web canvas, for adding various types of ‘Card’. We love a challenge so got to work on the requirements:</p>

<ul>
  <li>Panning and Zooming</li>
  <li>Add Cards from a pop up tray</li>
  <li>Cards should not overlap</li>
  <li>Drag Cards on the canvas</li>
</ul>

<h2 id="too-long-didnt-read-tldr">Too long didn’t read (TLDR)</h2>

<p>This was a complex epic, and there was definitely some head scratching. However we finished it on time and the end result is beautiful, responsive and fully covered by end to end tests. We used <a href="https://dndkit.com/">DndKit</a> for dragging and dropping, <a href="https://github.com/d3/d3-zoom">D3 Zoom</a> for panning and zooming and <a href="https://www.cypress.io/">Cypress</a> for the tests, which were all a pleasure to work with, and we would do so again.</p>

<h2 id="panning-and-zooming">Panning and Zooming</h2>

<p><img src="/assets/ceddlyburge/visual-sort/zoom-pan.gif" alt="Zooming and panning" /></p>

<p>A very stripped down version of the Canvas component is shown below, with the code required to hook up to D3 Zoom, and to apply the tranform.</p>

<p>The transform is applied to the canvas element, and the browser will automatically apply the transform to anything under it in the DOM tree, so nothing on the canvas needs to do anything.</p>

<div class="language-tsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">const</span> <span class="nx">Canvas</span> <span class="o">=</span> <span class="p">({</span> <span class="nx">children</span> <span class="p">}:</span> <span class="nx">CanvasProps</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">canvasRef</span> <span class="o">=</span> <span class="nx">useRef</span><span class="o">&lt;</span><span class="nx">HTMLDivElement</span> <span class="o">|</span> <span class="kc">null</span><span class="o">&gt;</span><span class="p">(</span><span class="kc">null</span><span class="p">);</span>

  <span class="c1">// store the current transform from d3</span>
  <span class="kd">const</span> <span class="p">[</span><span class="nx">transform</span><span class="p">,</span> <span class="nx">setTransform</span><span class="p">]</span> <span class="o">=</span> <span class="nf">useState</span><span class="p">(</span><span class="nx">d3</span><span class="p">.</span><span class="nx">zoomIdentity</span><span class="p">);</span>

  <span class="c1">// update the transform when d3 zoom notifies of a change</span>
  <span class="kd">const</span> <span class="nx">updateTransform</span> <span class="o">=</span> <span class="p">({</span> <span class="nx">transform</span> <span class="p">}:</span> <span class="p">{</span> <span class="na">transform</span><span class="p">:</span> <span class="nx">d3</span><span class="p">.</span><span class="nx">ZoomTransform</span> <span class="p">})</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="nf">setTransform</span><span class="p">(</span><span class="nx">transform</span><span class="p">);</span>
  <span class="p">};</span>

  <span class="c1">// create the d3 zoom object, and useMemo to retain it for rerenders</span>
  <span class="kd">const</span> <span class="nx">zoomBehavior</span> <span class="o">=</span> <span class="nf">useMemo</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="nx">d3</span><span class="p">.</span><span class="nx">zoom</span><span class="o">&lt;</span><span class="nx">HTMLDivElement</span><span class="p">,</span> <span class="nx">unknown</span><span class="o">&gt;</span><span class="p">(),</span> <span class="p">[]);</span>

  <span class="nf">useLayoutEffect</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="k">if </span><span class="p">(</span><span class="o">!</span><span class="nx">canvasRef</span><span class="p">.</span><span class="nx">current</span><span class="p">)</span> <span class="k">return</span><span class="p">;</span>

    <span class="c1">// get transform changed notifications from d3 zoom</span>
    <span class="nx">zoomBehavior</span><span class="p">.</span><span class="nf">on</span><span class="p">(</span><span class="dl">"</span><span class="s2">zoom</span><span class="dl">"</span><span class="p">,</span> <span class="nx">updateTransform</span><span class="p">);</span>

    <span class="c1">// attach d3 zoom to the canvas div element, which will handle</span>
    <span class="c1">// mousewheel and drag events automatically for pan / zoom</span>
    <span class="k">return</span> <span class="nx">d3</span>
      <span class="p">.</span><span class="nx">select</span><span class="o">&lt;</span><span class="nx">HTMLDivElement</span><span class="p">,</span> <span class="nx">unknown</span><span class="o">&gt;</span><span class="p">(</span><span class="nx">canvasRef</span><span class="p">.</span><span class="nx">current</span><span class="p">)</span>
      <span class="p">.</span><span class="nf">call</span><span class="p">(</span><span class="nx">zoomBehavior</span><span class="p">);</span>
  <span class="p">},</span> <span class="p">[</span><span class="nx">zoomBehavior</span><span class="p">,</span> <span class="nx">canvasRef</span><span class="p">]);</span>

  <span class="c1">// animated Zoom In, which can be called from a button event (not shown in this example)</span>
  <span class="kd">const</span> <span class="nx">zoomIn</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="nx">d3</span><span class="p">.</span><span class="nf">transition</span><span class="p">()?.</span><span class="nf">call</span><span class="p">(</span><span class="nx">zoomBehavior</span><span class="p">.</span><span class="nx">scaleBy</span><span class="p">,</span> <span class="mf">1.5</span><span class="p">);</span>
  <span class="p">};</span>

  <span class="k">return </span><span class="p">(</span>
    <span class="p">&lt;</span><span class="nt">div</span> <span class="na">ref</span><span class="p">=</span><span class="si">{</span><span class="nx">canvasRef</span><span class="si">}</span><span class="p">&gt;</span>
      <span class="p">&lt;</span><span class="nt">div</span>
        <span class="na">style</span><span class="p">=</span><span class="si">{</span>
          <span class="c1">// apply the transform from d3</span>
          <span class="nx">transformOrigin</span><span class="p">:</span> <span class="dl">"</span><span class="s2">top left</span><span class="dl">"</span><span class="p">,</span>
          <span class="nx">transform</span><span class="p">:</span> <span class="s2">`translate3d(</span><span class="p">${</span><span class="nx">transform</span><span class="p">.</span><span class="nx">x</span><span class="p">}</span><span class="s2">, </span><span class="p">${</span><span class="nx">transform</span><span class="p">.</span><span class="nx">y</span><span class="p">}</span><span class="s2">, </span><span class="p">${</span><span class="nx">transform</span><span class="p">.</span><span class="nx">k</span><span class="p">}</span><span class="s2">)`</span><span class="p">,</span>
        <span class="si">}</span>
      <span class="p">&gt;</span>
        <span class="si">{</span><span class="nx">children</span><span class="si">}</span>
      <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
    <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
  <span class="p">);</span>
<span class="p">};</span>
</code></pre></div></div>

<h2 id="drag-drop-from-tray">Drag Drop from Tray</h2>

<p>In order to create insightful visual arrangements, users wanted to be able to see all of their cards in a tray, and to drag them from there on to the canvas. None of the DndKit examples were that close to what we wanted, so we had to strike out on our own (although the documentation is excellent, which made things easier.)</p>

<p><img src="/assets/ceddlyburge/visual-sort/drag-drop-from-tray.png" alt="Drag drop from tray" /></p>

<h3 id="display-cards-on-the-canvas">Display cards on the canvas</h3>

<p>When an item is drag / dropped from the tray it needs to appear on the canvas, so firstly we simply hard coded a list of cards to display. These had an <code class="language-plaintext highlighter-rouge">x, y</code> position on the canvas, and all the information they needed to render. We sized the cards to match the grid size, and used the code below to position the cards on the canvas.</p>

<div class="language-tsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">&lt;</span><span class="nt">div</span>
  <span class="na">css</span><span class="p">=</span><span class="si">{</span>
    <span class="nx">position</span><span class="p">:</span> <span class="dl">"</span><span class="s2">absolute</span><span class="dl">"</span><span class="p">,</span>
    <span class="nx">origin</span><span class="p">:</span> <span class="dl">"</span><span class="s2">top left</span><span class="dl">"</span><span class="p">,</span>
    <span class="nx">top</span><span class="p">:</span> <span class="s2">`</span><span class="p">${</span><span class="nx">pixelCoordinates</span><span class="p">.</span><span class="nx">y</span><span class="p">}</span><span class="s2">px`</span><span class="p">,</span>
    <span class="nx">left</span><span class="p">:</span> <span class="s2">`</span><span class="p">${</span><span class="nx">pixelCoordinates</span><span class="p">.</span><span class="nx">x</span><span class="p">}</span><span class="s2">px`</span><span class="p">,</span>
  <span class="si">}</span>
<span class="p">&gt;</span>
  <span class="si">{</span><span class="nx">children</span><span class="si">}</span>
<span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</code></pre></div></div>

<h3 id="allow-dropping-when-dragged-over-the-canvas">Allow dropping when dragged over the canvas</h3>

<p>In our UI, the tray popped up over the canvas, and being as the canvas was a drop target, it was initially possible to drop a card on to the canvas without having first dragged it off the tray.</p>

<p>To fix this we created a custom strategy to work out the drop target by composing existing DndKit strategies (<a href="https://docs.dndkit.com/api-documentation/context-provider/collision-detection-algorithms#building-custom-collision-detection-algorithms">as recommended by the documentation</a>).</p>

<p>We first check to see if the current drag position is intersecting with the tray, and if so we return that. If not we fallback to the standard DndKit behaviour. This requires us to set up the tray as a drop target, and for the drop event to check what drop target was found (and to ignore the tray if this is the target).</p>

<p>The code for the custom strategy is like this</p>

<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">customCollisionDetectionStrategy</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="k">return </span><span class="p">(</span><span class="na">args</span><span class="p">:</span> <span class="p">{</span>
    <span class="na">active</span><span class="p">:</span> <span class="nx">Active</span><span class="p">;</span>
    <span class="nl">collisionRect</span><span class="p">:</span> <span class="nx">ViewRect</span><span class="p">;</span>
    <span class="nl">droppableContainers</span><span class="p">:</span> <span class="nx">DroppableContainer</span><span class="p">[];</span>
  <span class="p">})</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="k">if </span><span class="p">(</span><span class="nx">args</span><span class="p">.</span><span class="nx">active</span><span class="p">.</span><span class="nx">rect</span><span class="p">.</span><span class="nx">current</span><span class="p">.</span><span class="nx">translated</span><span class="p">)</span> <span class="p">{</span>
      <span class="kd">const</span> <span class="na">targetScaled</span><span class="p">:</span> <span class="nx">ViewRect</span> <span class="o">=</span> <span class="p">{</span>
        <span class="p">...</span><span class="nx">args</span><span class="p">.</span><span class="nx">active</span><span class="p">.</span><span class="nx">rect</span><span class="p">.</span><span class="nx">current</span><span class="p">.</span><span class="nx">translated</span><span class="p">,</span>
      <span class="p">};</span>

      <span class="kd">const</span> <span class="nx">trayRect</span> <span class="o">=</span> <span class="nx">args</span><span class="p">.</span><span class="nx">droppableContainers</span><span class="p">.</span><span class="nf">filter</span><span class="p">(</span>
        <span class="p">(</span><span class="nx">droppableContainer</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">droppableContainer</span><span class="p">.</span><span class="nx">id</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">tray</span><span class="dl">"</span>
      <span class="p">);</span>

      <span class="kd">const</span> <span class="nx">intersectingTrayRect</span> <span class="o">=</span> <span class="nf">rectIntersection</span><span class="p">({</span>
        <span class="na">active</span><span class="p">:</span> <span class="nx">args</span><span class="p">.</span><span class="nx">active</span><span class="p">,</span>
        <span class="na">collisionRect</span><span class="p">:</span> <span class="nx">targetScaled</span><span class="p">,</span>
        <span class="na">droppableContainers</span><span class="p">:</span> <span class="nx">trayRect</span><span class="p">,</span>
      <span class="p">});</span>

      <span class="k">if </span><span class="p">(</span><span class="nx">intersectingTrayRect</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">return</span> <span class="nx">intersectingTrayRect</span><span class="p">;</span>
      <span class="p">}</span>

      <span class="kd">const</span> <span class="nx">otherRects</span> <span class="o">=</span> <span class="nx">args</span><span class="p">.</span><span class="nx">droppableContainers</span><span class="p">.</span><span class="nf">filter</span><span class="p">(</span>
        <span class="p">(</span><span class="nx">droppableContainer</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">droppableContainer</span><span class="p">.</span><span class="nx">id</span> <span class="o">!==</span> <span class="dl">"</span><span class="s2">tray</span><span class="dl">"</span>
      <span class="p">);</span>

      <span class="k">return</span> <span class="nf">rectIntersection</span><span class="p">({</span>
        <span class="na">active</span><span class="p">:</span> <span class="nx">args</span><span class="p">.</span><span class="nx">active</span><span class="p">,</span>
        <span class="na">collisionRect</span><span class="p">:</span> <span class="nx">targetScaled</span><span class="p">,</span>
        <span class="na">droppableContainers</span><span class="p">:</span> <span class="nx">otherRects</span><span class="p">,</span>
      <span class="p">});</span>
    <span class="p">}</span>

    <span class="k">return</span> <span class="dl">""</span><span class="p">;</span>
  <span class="p">};</span>
<span class="p">};</span>
</code></pre></div></div>

<h3 id="the-drag-overlay--mouse-cursor-should-size-based-on-the-currrent-zoom-level-of-the-canvas-so-that-it-displays-as-it-will-appear-on-the-canvas">The drag overlay / mouse cursor should size based on the currrent zoom level of the canvas (so that it displays as it will appear on the canvas)</h3>

<p>The Canvas has a <a href="https://github.com/d3/d3-zoom#zoom-transforms">transform property (from D3)</a>, which has an <code class="language-plaintext highlighter-rouge">x</code> and a <code class="language-plaintext highlighter-rouge">y</code> property to define the panning, and a <code class="language-plaintext highlighter-rouge">k</code> property to define the zoom.</p>

<p>We already had canvas card components from earlier, but the zoom transform was being applied to the parent canvas component, so we still needed to size the drag overlay correctly.</p>

<p>This was achieved using the same scale transform that was applied to the canvas:</p>

<div class="language-tsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">&lt;</span><span class="nt">div</span>
    <span class="na">style</span><span class="si">{</span>
        <span class="nx">transformOrigin</span><span class="p">:</span> <span class="dl">'</span><span class="s1">top left</span><span class="dl">'</span><span class="p">,</span>
        <span class="nx">transform</span><span class="p">:</span> <span class="s2">`scale(</span><span class="p">${</span><span class="nx">transform</span><span class="p">.</span><span class="nx">k</span><span class="p">}</span><span class="s2">)`</span><span class="p">,</span>
      <span class="si">}</span>
<span class="p">&gt;</span>
</code></pre></div></div>

<h3 id="calculate-canvas-position-of-dropped-cards">Calculate canvas position of dropped cards</h3>

<p>To work out the position on the canvas, we need to know a few things:</p>

<ul>
  <li>The zoom level of the canvas</li>
  <li>The panning position of the canvas (relative to the window / viewport)</li>
  <li>The drop position (relative to the window / viewport)</li>
</ul>

<p>The canvas position is then <code class="language-plaintext highlighter-rouge">(panning position - drop position) / zoom</code></p>

<p>We already store and have access to the zoom level and the panning position of the canvas, but the drop position is a bit trickier.</p>

<p>The DndKit drop event gives us the delta of the drag operation, but sadly doesn’t give us the initial position of the drag. It does however allow us to attach some custom data via a <code class="language-plaintext highlighter-rouge">ref</code> in <code class="language-plaintext highlighter-rouge">useDraggable</code>, so we store <code class="language-plaintext highlighter-rouge">getBoundingClientRect()</code> as <code class="language-plaintext highlighter-rouge">initialRect</code>, and can access it in the drop event with <code class="language-plaintext highlighter-rouge">active.data.current.initialRect</code>. This allows us to calculate the window / viewport drop position, which then allows us to calculate the drop position on the canvas.</p>

<p>The full code looks like this. <code class="language-plaintext highlighter-rouge">transform</code> controls the pan (<code class="language-plaintext highlighter-rouge">x, y</code>) and zoom (<code class="language-plaintext highlighter-rouge">k</code>) of the canvas.</p>

<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">calculateCanvasPosition</span> <span class="o">=</span> <span class="p">(</span>
    <span class="nx">initialRect</span><span class="p">:</span> <span class="nx">DOMRect</span><span class="p">,</span>
    <span class="nx">over</span><span class="p">:</span> <span class="nx">Over</span><span class="p">,</span>
    <span class="nx">delta</span><span class="p">:</span> <span class="nx">Translate</span>
<span class="p">)</span> <span class="o">=&gt;</span>
  <span class="nf">scaleCoordinates</span><span class="p">(</span>
    <span class="p">{</span>
      <span class="na">x</span><span class="p">:</span> <span class="nx">initialRect</span><span class="p">.</span><span class="nx">x</span> <span class="o">+</span> <span class="nx">delta</span><span class="p">.</span><span class="nx">x</span> <span class="o">-</span> <span class="p">(</span><span class="nx">over</span><span class="p">?.</span><span class="nx">rect</span><span class="p">?.</span><span class="nx">offsetLeft</span> <span class="o">??</span> <span class="mi">0</span><span class="p">)</span> <span class="o">-</span> <span class="nx">transform</span><span class="p">.</span><span class="nx">x</span><span class="p">,</span>
      <span class="na">y</span><span class="p">:</span> <span class="nx">initialRect</span><span class="p">.</span><span class="nx">y</span> <span class="o">+</span> <span class="nx">delta</span><span class="p">.</span><span class="nx">y</span> <span class="o">-</span> <span class="p">(</span><span class="nx">over</span><span class="p">?.</span><span class="nx">rect</span><span class="p">?.</span><span class="nx">offsetTop</span> <span class="o">??</span> <span class="mi">0</span><span class="p">)</span> <span class="o">-</span> <span class="nx">transform</span><span class="p">.</span><span class="nx">y</span><span class="p">,</span>
    <span class="p">},</span>
    <span class="nx">transform</span><span class="p">.</span><span class="nx">k</span>
  <span class="p">);</span>

<span class="kd">const</span> <span class="nx">scaleCoordinates</span> <span class="o">=</span> <span class="p">(</span><span class="nx">coords</span><span class="p">:</span> <span class="nx">Coordinates</span><span class="p">,</span> <span class="nx">scale</span><span class="p">:</span> <span class="kr">number</span><span class="p">):</span> <span class="nx">Coordinates</span> <span class="o">=&gt;</span>
  <span class="p">{</span>
    <span class="na">x</span><span class="p">:</span> <span class="nx">coords</span><span class="p">.</span><span class="nx">x</span> <span class="o">/</span> <span class="nx">scale</span><span class="p">,</span>
    <span class="na">y</span><span class="p">:</span> <span class="nx">coords</span><span class="p">.</span><span class="nx">y</span> <span class="o">/</span> <span class="nx">scale</span><span class="p">,</span>
  <span class="p">};</span>
</code></pre></div></div>

<h3 id="snap-to-grid">Snap to grid</h3>

<p>Once we have worked out a position on the grid, snapping to a grid is trivial! We just need to decide on the grid size, and then round the coordinates to it. There is even a <a href="https://docs.dndkit.com/api-documentation/modifiers#snap-to-grid">nice example in the DnDKit docs</a>.</p>

<p>Our code looked like this</p>

<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">const</span> <span class="nx">snapCoordinates</span> <span class="o">=</span> <span class="p">({</span> <span class="nx">x</span><span class="p">,</span> <span class="nx">y</span> <span class="p">}:</span> <span class="nx">Coordinates</span><span class="p">):</span> <span class="nx">Coordinates</span> <span class="o">=&gt;</span> <span class="p">({</span>
  <span class="na">x</span><span class="p">:</span> <span class="nf">snapCoordinate</span><span class="p">(</span><span class="nx">x</span><span class="p">,</span> <span class="nx">gridSize</span><span class="p">),</span>
  <span class="na">y</span><span class="p">:</span> <span class="nf">snapCoordinate</span><span class="p">(</span><span class="nx">y</span><span class="p">,</span> <span class="nx">gridSize</span><span class="p">),</span>
<span class="p">});</span>

<span class="kd">const</span> <span class="nx">snapCoordinate</span> <span class="o">=</span> <span class="p">(</span><span class="nx">value</span><span class="p">:</span> <span class="kr">number</span><span class="p">,</span> <span class="nx">gridSize</span><span class="p">:</span> <span class="kr">number</span><span class="p">)</span> <span class="o">=&gt;</span>
  <span class="nb">Math</span><span class="p">.</span><span class="nf">round</span><span class="p">(</span><span class="nx">value</span> <span class="o">/</span> <span class="nx">gridSize</span><span class="p">)</span> <span class="o">*</span> <span class="nx">gridSize</span><span class="p">;</span>
</code></pre></div></div>

<h3 id="drag-and-drop">Drag and drop</h3>

<p>Once we have all these items in place, we can integrate with DndKit.</p>

<p>There is a <code class="language-plaintext highlighter-rouge">DndContext</code>, that DndKit uses to store all the state:</p>

<div class="language-tsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">&lt;</span><span class="nc">DndContext</span>
  <span class="na">sensors</span><span class="p">=</span><span class="si">{</span><span class="nx">sensors</span><span class="si">}</span>
  <span class="na">onDragStart</span><span class="p">=</span><span class="si">{</span><span class="nx">handleDragStart</span><span class="si">}</span> <span class="c1">// stores the activeCard</span>
  <span class="na">onDragMove</span><span class="p">=</span><span class="si">{</span><span class="nx">handleDragMove</span><span class="si">}</span> <span class="c1">// uses doCardsCollide (see "Cards should not overlap" later)</span>
  <span class="na">onDragEnd</span><span class="p">=</span><span class="si">{</span><span class="nx">handleDragEnd</span><span class="si">}</span> <span class="c1">// uses calculateCanvasPosition, adds activeCard to children</span>
  <span class="na">collisionDetection</span><span class="p">=</span><span class="si">{</span><span class="nf">customCollisionDetectionStrategy</span><span class="p">()</span><span class="si">}</span>
<span class="p">&gt;</span>
  <span class="si">{</span><span class="nx">children</span><span class="si">}</span>
<span class="p">&lt;/</span><span class="nc">DndContext</span><span class="p">&gt;</span>
</code></pre></div></div>

<p>Then each component on the tray can <code class="language-plaintext highlighter-rouge">useDraggable</code> to enable drag and drop.</p>

<div class="language-tsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">const</span> <span class="nx">Addable</span> <span class="o">=</span> <span class="p">({</span> <span class="nx">id</span><span class="p">,</span> <span class="nx">children</span> <span class="p">}:</span> <span class="nx">Props</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="p">[</span><span class="nx">ref</span><span class="p">,</span> <span class="nx">setRef</span><span class="p">]</span> <span class="o">=</span> <span class="nx">useState</span><span class="o">&lt;</span><span class="nx">Element</span> <span class="o">|</span> <span class="kc">null</span><span class="o">&gt;</span><span class="p">(</span><span class="kc">null</span><span class="p">);</span>

  <span class="kd">const</span> <span class="p">{</span> <span class="nx">attributes</span><span class="p">,</span> <span class="nx">listeners</span><span class="p">,</span> <span class="nx">setNodeRef</span> <span class="p">}</span> <span class="o">=</span> <span class="nf">useDraggable</span><span class="p">({</span>
    <span class="nx">id</span><span class="p">,</span>
    <span class="na">data</span><span class="p">:</span> <span class="p">{</span> <span class="na">initialRect</span><span class="p">:</span> <span class="nx">ref</span><span class="p">?.</span><span class="nf">getBoundingClientRect</span><span class="p">()</span> <span class="p">},</span>
  <span class="p">});</span>

  <span class="kd">const</span> <span class="nx">updateInitialRectAndForwardRef</span> <span class="o">=</span> <span class="p">(</span><span class="na">element</span><span class="p">:</span> <span class="nx">HTMLDivElement</span> <span class="o">|</span> <span class="kc">null</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="nf">setRef</span><span class="p">(</span><span class="nx">element</span><span class="p">);</span>
    <span class="nf">setNodeRef</span><span class="p">(</span><span class="nx">element</span><span class="p">);</span>
  <span class="p">};</span>

  <span class="k">return </span><span class="p">(</span>
    <span class="p">&lt;</span><span class="nt">div</span> <span class="na">ref</span><span class="p">=</span><span class="si">{</span><span class="nx">updateInitialRectAndForwardRef</span><span class="si">}</span> <span class="si">{</span><span class="p">...</span><span class="nx">listeners</span><span class="si">}</span> <span class="si">{</span><span class="p">...</span><span class="nx">attributes</span><span class="si">}</span><span class="p">&gt;</span>
      <span class="si">{</span><span class="nx">children</span><span class="si">}</span>
    <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
  <span class="p">);</span>
<span class="p">};</span>
</code></pre></div></div>

<h2 id="drag-cards-on-the-canvas">Drag Cards on the canvas</h2>

<p>Once the cards are on the canvas, we can use DndKit again to make them draggable. This is a bit different to dragging / dropping from the tray, as nothing new gets added to the canvas, and instead an existing item changes position.</p>

<p>The <code class="language-plaintext highlighter-rouge">DndContext</code> is much the same as before</p>

<div class="language-tsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">&lt;</span><span class="nc">DndContext</span>
  <span class="na">sensors</span><span class="p">=</span><span class="si">{</span><span class="nx">sensors</span><span class="si">}</span>
  <span class="na">onDragStart</span><span class="p">=</span><span class="si">{</span><span class="nx">handleDragStart</span><span class="si">}</span> <span class="c1">// stores the activeCard</span>
  <span class="na">onDragMove</span><span class="p">=</span><span class="si">{</span><span class="nx">handleDragMove</span><span class="si">}</span> <span class="c1">// uses doCardsCollide (see "Cards should not overlap" later), updates pixelCoordinates</span>
  <span class="na">onDragEnd</span><span class="p">=</span><span class="si">{</span><span class="nx">handleDragEnd</span><span class="si">}</span> <span class="c1">// updates position of activeCard</span>
<span class="p">&gt;</span>
  <span class="si">{</span><span class="nx">children</span><span class="si">}</span>
<span class="p">&lt;/</span><span class="nc">DndContext</span><span class="p">&gt;</span>
</code></pre></div></div>

<p>The cards on the canvas are slightly more complex, as they have to position themselves on the canvas, and update their position temporarily while they are being dragged.</p>

<div class="language-tsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">const</span> <span class="nx">Draggable</span> <span class="o">=</span> <span class="p">({</span>
  <span class="nx">id</span><span class="p">,</span>
  <span class="nx">pixelCoordinates</span><span class="p">,</span>
  <span class="nx">k</span><span class="p">,</span>
  <span class="nx">children</span><span class="p">,</span>
<span class="p">}:</span> <span class="nx">DraggableProps</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="p">{</span> <span class="nx">attributes</span><span class="p">,</span> <span class="nx">listeners</span><span class="p">,</span> <span class="nx">setNodeRef</span><span class="p">,</span> <span class="nx">transform</span> <span class="p">}</span> <span class="o">=</span> <span class="nf">useDraggable</span><span class="p">({</span>
    <span class="nx">id</span><span class="p">,</span>
    <span class="na">data</span><span class="p">:</span> <span class="p">{</span> <span class="nx">pixelCoordinates</span><span class="p">,</span> <span class="nx">id</span><span class="p">,</span> <span class="nx">ownId</span> <span class="p">},</span>
  <span class="p">});</span>

  <span class="k">return </span><span class="p">(</span>
    <span class="p">&lt;</span><span class="nt">div</span>
      <span class="c1">// position of card on canvas</span>
      <span class="na">css</span><span class="p">=</span><span class="si">{</span>
        <span class="nx">position</span><span class="p">:</span> <span class="dl">"</span><span class="s2">absolute</span><span class="dl">"</span><span class="p">,</span>
        <span class="nx">origin</span><span class="p">:</span> <span class="dl">"</span><span class="s2">top left</span><span class="dl">"</span><span class="p">,</span>
        <span class="nx">top</span><span class="p">:</span> <span class="s2">`</span><span class="p">${</span><span class="nx">pixelCoordinates</span><span class="p">.</span><span class="nx">y</span><span class="p">}</span><span class="s2">px`</span><span class="p">,</span>
        <span class="nx">left</span><span class="p">:</span> <span class="s2">`</span><span class="p">${</span><span class="nx">pixelCoordinates</span><span class="p">.</span><span class="nx">x</span><span class="p">}</span><span class="s2">px`</span><span class="p">,</span>
      <span class="si">}</span>
      <span class="c1">// temporary change to this position when dragging</span>
      <span class="na">style</span><span class="p">=</span><span class="si">{</span>
        <span class="nx">transform</span>
          <span class="p">?</span> <span class="p">{</span> <span class="na">transform</span><span class="p">:</span> <span class="s2">`translate3d(</span><span class="p">${</span><span class="nx">transform</span><span class="p">.</span><span class="nx">x</span><span class="p">}</span><span class="s2">, </span><span class="p">${</span><span class="nx">transform</span><span class="p">.</span><span class="nx">y</span><span class="p">}</span><span class="s2">, 0)`</span> <span class="p">}</span>
          <span class="p">:</span> <span class="p">{}</span>
      <span class="si">}</span>
      <span class="na">ref</span><span class="p">=</span><span class="si">{</span><span class="nx">setNodeRef</span><span class="si">}</span>
      <span class="si">{</span><span class="p">...</span><span class="nx">listeners</span><span class="si">}</span>
      <span class="si">{</span><span class="p">...</span><span class="nx">attributes</span><span class="si">}</span>
    <span class="p">&gt;</span>
      <span class="si">{</span><span class="nx">children</span><span class="si">}</span>
    <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
  <span class="p">);</span>
<span class="p">};</span>
</code></pre></div></div>

<h2 id="cards-should-not-overlap">Cards should not overlap</h2>

<p>One of the requirements was that cards should not overlap on the canvas, so we needed to detect when collissions would occur and prevent them.</p>

<p>There are two collision detection scenarios, when drag dropping from the tray, and when dragging around the canvas. The 2 situations are very similar, the main differences being that the calculation of the canvas position is different when dropping from the tray, and a card being dragged around the canvas doesn’t need to worry about colliding with itself.</p>

<p>The cards themselves are square, so the code to detect whether two cards collide is trivial. The one minor complication is that the collission detection has to take place after the coordinates are snapped to the grid.</p>

<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">doCardsCollide</span> <span class="o">=</span> <span class="p">(</span><span class="nx">card1</span><span class="p">:</span> <span class="nx">Coordinates</span><span class="p">,</span> <span class="nx">card2</span><span class="p">:</span> <span class="nx">Coordinates</span><span class="p">)</span> <span class="o">=&gt;</span>
  <span class="nb">Math</span><span class="p">.</span><span class="nf">abs</span><span class="p">(</span><span class="nx">card1</span><span class="p">.</span><span class="nx">x</span> <span class="o">-</span> <span class="nx">card2</span><span class="p">.</span><span class="nx">x</span><span class="p">)</span> <span class="o">&lt;</span> <span class="nx">cardSize</span> <span class="o">&amp;&amp;</span>
  <span class="nb">Math</span><span class="p">.</span><span class="nf">abs</span><span class="p">(</span><span class="nx">card1</span><span class="p">.</span><span class="nx">y</span> <span class="o">-</span> <span class="nx">card2</span><span class="p">.</span><span class="nx">y</span><span class="p">)</span> <span class="o">&lt;</span> <span class="nx">cardSize</span><span class="p">;</span>
</code></pre></div></div>

<p>When dragging, if a card on the canvas would collide, we add a red overlay to it. When dragging around the canvas, we show the last known good position of a card with a dashed outline, which is where the card will go if it is dropped. This updates as a card is dragged, and snaps to the grid. Where a card on the canvas would cause a collission, the last known position simply stays where it is, until the dragged card is moved in to a collission free space.</p>

<p><img src="/assets/ceddlyburge/visual-sort/collision-detection.png" alt="Collision detection" /></p>

<h2 id="testing">Testing</h2>

<p>We added <a href="https://docs.cypress.io/api/cypress-api/custom-commands">Cypress Custom Commands</a>, like the one below to make it easy to write end to end tests.</p>

<p>The <a href="https://docs.cypress.io/api/commands/wrap">wrap</a> command turns a jquery object in to a cypress object (that you can then chain other cypress comannds off), and the <a href="https://docs.cypress.io/api/commands/trigger">trigger</a> command creates simulated events. There is a slight annoyance in that there are quite a few events triggered in response to various mouse operations, but it is all encapsulated in the custom command so writing the tests is still easy. <code class="language-plaintext highlighter-rouge">{ prevSubject: 'element' }</code> specifies that the <code class="language-plaintext highlighter-rouge">dragOntoCanvas</code> command can only be chained off cypress commands that yield <code class="language-plaintext highlighter-rouge">element</code>’s.</p>

<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">Cypress</span><span class="p">.</span><span class="nx">Commands</span><span class="p">.</span><span class="nf">add</span><span class="p">(</span>
  <span class="dl">"</span><span class="s2">dragOntoCanvas</span><span class="dl">"</span><span class="p">,</span>
  <span class="p">{</span> <span class="na">prevSubject</span><span class="p">:</span> <span class="dl">"</span><span class="s2">element</span><span class="dl">"</span> <span class="p">},</span>
  <span class="p">(</span>
    <span class="nx">item</span><span class="p">:</span> <span class="nx">JQuery</span><span class="o">&lt;</span><span class="nx">HTMLElement</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="p">{</span> <span class="nx">startCoordinates</span><span class="p">,</span> <span class="nx">endCoordinates</span> <span class="p">}:</span> <span class="nx">DragOntoCanvasOptions</span>
  <span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="c1">// `force: true` shouldn't be needed, but the tests think that</span>
    <span class="c1">// the drag overlay is covering the canvas (which is true) and</span>
    <span class="c1">// that this prevents mouse operations (which is false)</span>
    <span class="kd">const</span> <span class="nx">force</span> <span class="o">=</span> <span class="p">{</span> <span class="na">force</span><span class="p">:</span> <span class="kc">true</span> <span class="p">};</span>
    <span class="kd">const</span> <span class="nx">leftButton</span> <span class="o">=</span> <span class="p">{</span> <span class="na">button</span><span class="p">:</span> <span class="mi">0</span> <span class="p">};</span>

    <span class="kd">const</span> <span class="nx">dragStart</span> <span class="o">=</span> <span class="p">{</span> <span class="p">...</span><span class="nx">leftButton</span><span class="p">,</span> <span class="p">...</span><span class="nx">startCoordinates</span> <span class="p">};</span>
    <span class="kd">const</span> <span class="nx">dragOver</span> <span class="o">=</span> <span class="p">{</span> <span class="p">...</span><span class="nx">endCoordinates</span><span class="p">,</span> <span class="p">...</span><span class="nx">force</span> <span class="p">};</span>
    <span class="kd">const</span> <span class="nx">drop</span> <span class="o">=</span> <span class="p">{</span> <span class="p">...</span><span class="nx">leftButton</span><span class="p">,</span> <span class="p">...</span><span class="nx">endCoordinates</span><span class="p">,</span> <span class="p">...</span><span class="nx">force</span> <span class="p">};</span>

    <span class="kd">const</span> <span class="nx">pointerEvent</span> <span class="o">=</span> <span class="p">{</span> <span class="na">eventConstructor</span><span class="p">:</span> <span class="dl">"</span><span class="s2">PointerEvent</span><span class="dl">"</span> <span class="p">};</span>
    <span class="kd">const</span> <span class="nx">mouseEvent</span> <span class="o">=</span> <span class="p">{</span> <span class="na">eventConstructor</span><span class="p">:</span> <span class="dl">"</span><span class="s2">MouseEvent</span><span class="dl">"</span> <span class="p">};</span>
    <span class="kd">const</span> <span class="nx">dragEvent</span> <span class="o">=</span> <span class="p">{</span> <span class="na">eventConstructor</span><span class="p">:</span> <span class="dl">"</span><span class="s2">DragEvent</span><span class="dl">"</span> <span class="p">};</span>

    <span class="nx">cy</span><span class="p">.</span><span class="nf">wrap</span><span class="p">(</span><span class="nx">item</span><span class="p">)</span>
      <span class="p">.</span><span class="nf">trigger</span><span class="p">(</span><span class="dl">"</span><span class="s2">pointerdown</span><span class="dl">"</span><span class="p">,</span> <span class="p">{</span> <span class="p">...</span><span class="nx">pointerEvent</span><span class="p">,</span> <span class="p">...</span><span class="nx">dragStart</span> <span class="p">})</span>
      <span class="p">.</span><span class="nf">trigger</span><span class="p">(</span><span class="dl">"</span><span class="s2">mousedown</span><span class="dl">"</span><span class="p">,</span> <span class="p">{</span> <span class="p">...</span><span class="nx">mouseEvent</span><span class="p">,</span> <span class="p">...</span><span class="nx">dragStart</span> <span class="p">})</span>
      <span class="p">.</span><span class="nf">trigger</span><span class="p">(</span><span class="dl">"</span><span class="s2">dragstart</span><span class="dl">"</span><span class="p">,</span> <span class="p">{</span> <span class="p">...</span><span class="nx">dragEvent</span><span class="p">,</span> <span class="p">...</span><span class="nx">force</span> <span class="p">});</span>

    <span class="nx">cy</span><span class="p">.</span><span class="nf">findByTestId</span><span class="p">(</span><span class="dl">"</span><span class="s2">canvas</span><span class="dl">"</span><span class="p">)</span>
      <span class="p">.</span><span class="nf">trigger</span><span class="p">(</span><span class="dl">"</span><span class="s2">dragover</span><span class="dl">"</span><span class="p">,</span> <span class="p">{</span> <span class="p">...</span><span class="nx">dragEvent</span><span class="p">,</span> <span class="p">...</span><span class="nx">force</span> <span class="p">})</span>
      <span class="p">.</span><span class="nf">trigger</span><span class="p">(</span><span class="dl">"</span><span class="s2">mousemove</span><span class="dl">"</span><span class="p">,</span> <span class="p">{</span> <span class="p">...</span><span class="nx">mouseEvent</span><span class="p">,</span> <span class="p">...</span><span class="nx">dragOver</span> <span class="p">})</span>
      <span class="p">.</span><span class="nf">trigger</span><span class="p">(</span><span class="dl">"</span><span class="s2">pointermove</span><span class="dl">"</span><span class="p">,</span> <span class="p">{</span> <span class="p">...</span><span class="nx">pointerEvent</span><span class="p">,</span> <span class="p">...</span><span class="nx">dragOver</span> <span class="p">});</span>

    <span class="nx">cy</span><span class="p">.</span><span class="nf">findByTestId</span><span class="p">(</span><span class="dl">"</span><span class="s2">canvas</span><span class="dl">"</span><span class="p">)</span>
      <span class="p">.</span><span class="nf">trigger</span><span class="p">(</span><span class="dl">"</span><span class="s2">drop</span><span class="dl">"</span><span class="p">,</span> <span class="p">{</span> <span class="p">...</span><span class="nx">dragEvent</span><span class="p">,</span> <span class="p">...</span><span class="nx">force</span> <span class="p">})</span>
      <span class="p">.</span><span class="nf">trigger</span><span class="p">(</span><span class="dl">"</span><span class="s2">mouseup</span><span class="dl">"</span><span class="p">,</span> <span class="p">{</span> <span class="p">...</span><span class="nx">mouseEvent</span><span class="p">,</span> <span class="p">...</span><span class="nx">drop</span> <span class="p">})</span>
      <span class="p">.</span><span class="nf">trigger</span><span class="p">(</span><span class="dl">"</span><span class="s2">pointerup</span><span class="dl">"</span><span class="p">,</span> <span class="p">{</span> <span class="p">...</span><span class="nx">pointerEvent</span><span class="p">,</span> <span class="p">...</span><span class="nx">drop</span> <span class="p">});</span>
  <span class="p">}</span>
<span class="p">);</span>
</code></pre></div></div>

<p>We can then use the custom command in tests like this.</p>

<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">cy</span><span class="p">.</span><span class="nf">findAllByText</span><span class="p">(</span><span class="dl">"</span><span class="s2">cb894</span><span class="dl">"</span><span class="p">).</span><span class="nf">dragOntoCanvas</span><span class="p">({</span>
  <span class="na">start</span><span class="p">:</span> <span class="p">{</span> <span class="na">clientX</span><span class="p">:</span> <span class="mi">50</span><span class="p">,</span> <span class="na">clientY</span><span class="p">:</span> <span class="mi">50</span> <span class="p">},</span>
  <span class="na">end</span><span class="p">:</span> <span class="p">{</span> <span class="na">clientX</span><span class="p">:</span> <span class="mi">700</span><span class="p">,</span> <span class="na">clientY</span><span class="p">:</span> <span class="mi">200</span> <span class="p">},</span>
<span class="p">});</span>
</code></pre></div></div>

<h2 id="wrapping-up">Wrapping up</h2>

<p>So there with have it! A fully tested custom web canvas that you can drag cards to, and then rearrange.</p>

<p>It took around one sprint to spike, and then another to get to v1, and there have been subsequent iterations to add features and improve performance, which may become topics for future posts!</p>

<p>If this sounds like the sort of work you would like to do then <a href="https://red-badger.com/jobs/">come join us</a>, or if you are tackling a similar problem at your company please <a href="mailto:hello@red-badger.com">get in touch</a>.</p>]]></content><author><name>Cedd Burge</name></author><category term="ceddlyburge" /><summary type="html"><![CDATA[We recently worked with a client who asked us to create a freestyle web canvas, for adding various types of 'Card'.]]></summary></entry><entry><title type="html">Plotting Fractals in WebAssembly</title><link href="https://awesome.red-badger.com//chriswhealy/plotting-fractals-in-webassembly" rel="alternate" type="text/html" title="Plotting Fractals in WebAssembly" /><published>2021-12-07T12:00:00+00:00</published><updated>2021-12-07T12:00:00+00:00</updated><id>https://awesome.red-badger.com//chriswhealy/plotting-fractals-in-webassembly</id><content type="html" xml:base="https://awesome.red-badger.com//chriswhealy/plotting-fractals-in-webassembly"><![CDATA[<h2 id="introduction">Introduction</h2>

<p>This tutorial is a continuation of the earlier <a href="/chriswhealy/introduction-to-web-assembly-text">Introduction to WebAssembly Text</a></p>

<p>If you are not familiar with WebAssembly Text (WAT), then please read the above introductory tutorial first because from this point on, I will assume that you are at least able to read and understand a WebAssembly Text program.</p>

<p>In the tutorials that follow, we will take a detailed look at how to implement an application in WebAssembly Text that plots the Mandelbrot and Julia Sets</p>

<h2 id="but-why-not-just-write-the-solution-in-rust">But Why Not Just Write the Solution in Rust?</h2>

<p>I did <a href="https://github.com/chriswhealy/fractal_explorer">here</a>!</p>

<p>But here’s the thing…</p>

<p>When I wrote the above solution in Rust, I enjoyed all the advantages of using a language with much richer programming constructs and a compiler that turns out almost bullet-proof code.  However, when I used <a href="https://rustwasm.github.io/wasm-pack/installer/"><code class="language-plaintext highlighter-rouge">wasm-pack</code></a> to transform the Rust executable into a <code class="language-plaintext highlighter-rouge">.wasm</code> module, the resulting file was 74Kb in size.</p>

<p>This is certainly not large, but it was much larger than I expected given the simplicity of the task being performed.</p>

<p>So as a matter of both curiosity and education, I set about re-implementing this program in WebAssembly Text (WAT) to see just how small I could get it.</p>

<p>The results are encouraging because the hand-crafted <code class="language-plaintext highlighter-rouge">.wasm</code> file is now about 150 times smaller - just 493 bytes…</p>

<h2 id="live-demo">Live Demo</h2>

<p><a href="https://raw-wasm.pages.dev/">Plotting Fractals Using WebAssembly Threads and Web Workers</a></p>

<h1 id="table-of-contents">Table of Contents</h1>
<ol>
  <li><a href="/chriswhealy/FractalWASM/01%20Plotting%20Fractals/">Plotting Fractals</a></li>
  <li><a href="/chriswhealy/FractalWASM//02%20Initial%20Implementation/">Initial Implementation</a></li>
  <li><a href="/chriswhealy/FractalWASM//03%20WAT%20Basic%20Implementation/">Basic WAT Implementation</a></li>
  <li><a href="/chriswhealy/FractalWASM//04%20WAT%20Optimised%20Implementation/">Optimised WAT Implementation</a></li>
  <li><a href="/chriswhealy/FractalWASM//05%20MB%20Julia%20Set/">Plotting a Julia Set</a></li>
  <li><a href="/chriswhealy/FractalWASM//06%20Zoom%20Image/">Zooming In</a></li>
  <li><a href="/chriswhealy/FractalWASM//07%20Web%20Workers/">WebAssembly and Web Workers</a></li>
</ol>]]></content><author><name>Chris Whealy</name></author><category term="chriswhealy" /><summary type="html"><![CDATA[This set of blogs builds a progressively more optimised set of WebAssembly Text programs that plot the Mandelbrot and Julia Sets.]]></summary></entry></feed>