<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <id>https://mcclowes.com/blog</id>
    <title>@mcclowes Blog</title>
    <updated>2026-03-25T00:00:00.000Z</updated>
    <generator>https://github.com/jpmonette/feed</generator>
    <link rel="alternate" href="https://mcclowes.com/blog"/>
    <subtitle>@mcclowes Blog</subtitle>
    <icon>https://mcclowes.com/img/favicon.ico</icon>
    <entry>
        <title type="html"><![CDATA[Designing a consistent error handling pattern for APIs]]></title>
        <id>https://mcclowes.com/blog/2026/03/25/api-error-handling</id>
        <link href="https://mcclowes.com/blog/2026/03/25/api-error-handling"/>
        <updated>2026-03-25T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Errors and warnings are a minefield for developers, and very inconsistently implemented across APIs. I've been thinking about what a consistent, developer-friendly approach to surfacing issues looks like — one that helps both developers and their end users.]]></summary>
        <content type="html"><![CDATA[<p>Errors and warnings are a minefield for developers, and very inconsistently implemented across <span class="glossaryTermWrapper_tlxd"><a href="https://mcclowes.com/glossary#api" class="glossaryTerm_WE8X" aria-describedby="tooltip-api">APIs</a><span id="tooltip-api" class="tooltip_MOZH tooltipTop_HWYP tooltipFloating_XSLb" role="tooltip"><strong>API</strong> <!-- -->A set of rules and protocols that allows different software applications to communicate with each other. APIs define the methods and data formats that applications can use to request and exchange information.</span></span>. I've been thinking about what a consistent, developer-friendly approach to surfacing issues looks like — one that helps both developers and their end users.</p>
<p>This is focused on <span class="glossaryTermWrapper_tlxd"><a href="https://mcclowes.com/glossary#api" class="glossaryTerm_WE8X" aria-describedby="tooltip-api">API</a><span id="tooltip-api" class="tooltip_MOZH tooltipTop_HWYP tooltipFloating_XSLb" role="tooltip"><strong>API</strong> <!-- -->A set of rules and protocols that allows different software applications to communicate with each other. APIs define the methods and data formats that applications can use to request and exchange information.</span></span> responses and logs rather than native <span class="glossaryTermWrapper_tlxd"><a href="https://mcclowes.com/glossary#sdk" class="glossaryTerm_WE8X" aria-describedby="tooltip-sdk">SDKs</a><span id="tooltip-sdk" class="tooltip_MOZH tooltipTop_HWYP tooltipFloating_XSLb" role="tooltip"><strong>SDK</strong> <!-- -->A collection of software development tools, libraries, documentation, code samples, and guides that help developers create applications for a specific platform or framework.</span></span>.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="principles">Principles<a href="https://mcclowes.com/blog/2026/03/25/api-error-handling#principles" class="hash-link" aria-label="Direct link to Principles" title="Direct link to Principles" translate="no">​</a></h2>
<ol>
<li class=""><strong>The client can see what happened.</strong> No mystery failures.</li>
<li class=""><strong>The client can understand why and how to resolve it</strong> — with links to docs, support, and related resources.</li>
<li class=""><strong>The client can communicate the issue to their user.</strong> The <span class="glossaryTermWrapper_tlxd"><a href="https://mcclowes.com/glossary#api" class="glossaryTerm_WE8X" aria-describedby="tooltip-api">API</a><span id="tooltip-api" class="tooltip_MOZH tooltipTop_HWYP tooltipFloating_XSLb" role="tooltip"><strong>API</strong> <!-- -->A set of rules and protocols that allows different software applications to communicate with each other. APIs define the methods and data formats that applications can use to request and exchange information.</span></span> should help them do this.</li>
<li class=""><strong>The client isn't forced into complex change management.</strong> Breaking changes to error shapes are painful.</li>
<li class=""><strong>Issue persistence is captured where appropriate.</strong> Some issues are transient, some are ongoing.</li>
<li class=""><strong>Issue structure is consistent across contexts</strong> — <span class="glossaryTermWrapper_tlxd"><a href="https://mcclowes.com/glossary#api" class="glossaryTerm_WE8X" aria-describedby="tooltip-api">API</a><span id="tooltip-api" class="tooltip_MOZH tooltipTop_HWYP tooltipFloating_XSLb" role="tooltip"><strong>API</strong> <!-- -->A set of rules and protocols that allows different software applications to communicate with each other. APIs define the methods and data formats that applications can use to request and exchange information.</span></span> responses, webhooks, component callbacks, UI.</li>
<li class=""><strong>It's clear where action is required</strong> vs where the issue is advisory.</li>
</ol>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="the-issues-array">The <code>issues</code> array<a href="https://mcclowes.com/blog/2026/03/25/api-error-handling#the-issues-array" class="hash-link" aria-label="Direct link to the-issues-array" title="Direct link to the-issues-array" translate="no">​</a></h2>
<p>All non-success <span class="glossaryTermWrapper_tlxd"><a href="https://mcclowes.com/glossary#api" class="glossaryTerm_WE8X" aria-describedby="tooltip-api">API</a><span id="tooltip-api" class="tooltip_MOZH tooltipTop_HWYP tooltipFloating_XSLb" role="tooltip"><strong>API</strong> <!-- -->A set of rules and protocols that allows different software applications to communicate with each other. APIs define the methods and data formats that applications can use to request and exchange information.</span></span> responses should include an <code>issues</code> array. This surfaces errors, warnings, and informational notices in a consistent structure, giving developers enough context to understand what went wrong, whether it's resolved, and where to go next.</p>
<p>Errors and warnings share the same shape and the same need — context, traceability, a path forward — so splitting them into separate arrays would force developers to check two places for information that belongs to the same moment in a request's lifecycle.</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="example-response">Example response<a href="https://mcclowes.com/blog/2026/03/25/api-error-handling#example-response" class="hash-link" aria-label="Direct link to Example response" title="Direct link to Example response" translate="no">​</a></h3>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"issues"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"type"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"unauthorized"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"issue"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"unauthorized.token_expired"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"severity"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"error"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"correlationId"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"4b3a2c1d-0000-0000-0000-abcdef123456"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"dateTime"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"2024-11-01T12:34:56Z"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"active"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token boolean">false</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"message"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token property">"title"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Payment not authorised"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token property">"detail"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"This transaction couldn't be completed."</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"links"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token property">"documentation"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"https://docs.example.com/errors/unauthorized"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token property">"portal"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"https://support.example.com"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token property">"retry"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"https://api.example.com/..."</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="fields">Fields<a href="https://mcclowes.com/blog/2026/03/25/api-error-handling#fields" class="hash-link" aria-label="Direct link to Fields" title="Direct link to Fields" translate="no">​</a></h2>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="type"><code>type</code><a href="https://mcclowes.com/blog/2026/03/25/api-error-handling#type" class="hash-link" aria-label="Direct link to type" title="Direct link to type" translate="no">​</a></h3>
<p><strong>Type:</strong> <code>enum (string)</code> — <strong>Required:</strong> Yes</p>
<p>Top-level category of the issue. A stable, versioned set of values.</p>
<table><thead><tr><th>Value</th><th>Description</th></tr></thead><tbody><tr><td><code>unauthorized</code></td><td>Authentication or permission failure</td></tr><tr><td><code>validation</code></td><td>Request payload failed validation</td></tr><tr><td><code>conflict</code></td><td>State conflict (e.g. duplicate resource)</td></tr><tr><td><code>rate_limit</code></td><td>Too many requests</td></tr><tr><td><code>internal</code></td><td>Server-side error (e.g. 500s)</td></tr></tbody></table>
<p>Expand as the taxonomy matures.</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="issue"><code>issue</code><a href="https://mcclowes.com/blog/2026/03/25/api-error-handling#issue" class="hash-link" aria-label="Direct link to issue" title="Direct link to issue" translate="no">​</a></h3>
<p><strong>Type:</strong> <code>string (namespaced)</code> — <strong>Required:</strong> Yes</p>
<p>A more specific, machine-readable code within the <code>type</code>. Format: <code>{type}.{detail}</code> — e.g. <code>unauthorized.token_expired</code>, <code>validation.missing_field</code>.</p>
<p>I'd recommend keeping these as strings rather than strict enums initially, to allow the taxonomy to evolve without introducing breaking changes. Consumers should handle unknown values gracefully.</p>
<p>Namespaced codes let you express hierarchy without proliferating top-level values — <code>validation.missing_field</code> and <code>validation.invalid_format</code> are obviously related, where <code>missing_field</code> and <code>invalid_format</code> in isolation are just noise.</p>
<p>Prefer descriptive strings to numeric status and error codes.</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="correlationid"><code>correlationId</code><a href="https://mcclowes.com/blog/2026/03/25/api-error-handling#correlationid" class="hash-link" aria-label="Direct link to correlationid" title="Direct link to correlationid" translate="no">​</a></h3>
<p><strong>Type:</strong> <code>string (UUID)</code> — <strong>Required:</strong> Yes</p>
<p>A unique identifier for the request. Use this when raising a support ticket or querying logs — it's the fastest way to locate the issue on the server side.</p>
<p>The <span class="glossaryTermWrapper_tlxd"><a href="https://mcclowes.com/glossary#api" class="glossaryTerm_WE8X" aria-describedby="tooltip-api">API</a><span id="tooltip-api" class="tooltip_MOZH tooltipTop_HWYP tooltipFloating_XSLb" role="tooltip"><strong>API</strong> <!-- -->A set of rules and protocols that allows different software applications to communicate with each other. APIs define the methods and data formats that applications can use to request and exchange information.</span></span> should generate a <code>correlationId</code> for every request. If the client supplies an <code>X-Correlation-ID</code> header, that value is echoed back, allowing them to correlate server logs with their own.</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="severity"><code>severity</code><a href="https://mcclowes.com/blog/2026/03/25/api-error-handling#severity" class="hash-link" aria-label="Direct link to severity" title="Direct link to severity" translate="no">​</a></h3>
<p><strong>Type:</strong> <code>enum (string)</code> — <strong>Required:</strong> Yes</p>
<p>Whether the issue blocks the request or is advisory.</p>
<table><thead><tr><th>Value</th><th>Description</th></tr></thead><tbody><tr><td><code>error</code></td><td>Request failed; action required</td></tr><tr><td><code>warning</code></td><td>Request succeeded (or partially succeeded); attention advised</td></tr><tr><td><code>info</code></td><td>Informational notice only</td></tr></tbody></table>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="datetime"><code>dateTime</code><a href="https://mcclowes.com/blog/2026/03/25/api-error-handling#datetime" class="hash-link" aria-label="Direct link to datetime" title="Direct link to datetime" translate="no">​</a></h3>
<p><strong>Type:</strong> <code>ISO 8601 string</code> — <strong>Required:</strong> Yes</p>
<p>When the issue occurred, in UTC.</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="active"><code>active</code><a href="https://mcclowes.com/blog/2026/03/25/api-error-handling#active" class="hash-link" aria-label="Direct link to active" title="Direct link to active" translate="no">​</a></h3>
<p><strong>Type:</strong> <code>boolean</code> — <strong>Required:</strong> No</p>
<p>Whether the issue is still ongoing. This is key where issues are persistent or stateful rather than transient. Useful for platform-level problems (e.g. a service degradation) where the issue may resolve without developer action.</p>
<p>The idea is that a developer can disregard issues where <code>active === false</code>.</p>
<p>Some real-world examples:</p>
<ul>
<li class="">A connected device goes offline — this is communicated as an active issue until the connection is re-established.</li>
<li class="">A user's authorised access to a third-party data source expires or is revoked — this is an active issue until re-authorisation.</li>
</ul>
<p>Omit this field if resolution state cannot be reliably tracked. A stale <code>active: true</code> is more harmful than no signal at all.</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="message"><code>message</code><a href="https://mcclowes.com/blog/2026/03/25/api-error-handling#message" class="hash-link" aria-label="Direct link to message" title="Direct link to message" translate="no">​</a></h3>
<p><strong>Type:</strong> <code>object</code> — <strong>Required:</strong> No</p>
<p>A human-readable summary of the issue, suitable for surfacing to end users. Provided as a convenience — integrators may override this copy to match their own tone or context.</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"message"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">"title"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Payment not authorised"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">"detail"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"This transaction couldn't be completed. Please check your card details or contact support."</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre></div></div>
<table><thead><tr><th>Field</th><th>Description</th></tr></thead><tbody><tr><td><code>title</code></td><td>Short, stable label. Suitable for a toast or modal heading.</td></tr><tr><td><code>detail</code></td><td>Fuller explanation. Suitable for inline help text or a support flow.</td></tr></tbody></table>
<p>Copy is not localised — provided in English only. Localisation is the integrator's responsibility.</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="thirdparty"><code>thirdParty</code><a href="https://mcclowes.com/blog/2026/03/25/api-error-handling#thirdparty" class="hash-link" aria-label="Direct link to thirdparty" title="Direct link to thirdparty" translate="no">​</a></h3>
<p><strong>Type:</strong> <code>object</code> — <strong>Required:</strong> No</p>
<p>Present when the issue originates from a third-party service (e.g. a KYC or payment provider). Passed through as-is — the <span class="glossaryTermWrapper_tlxd"><a href="https://mcclowes.com/glossary#api" class="glossaryTerm_WE8X" aria-describedby="tooltip-api">API</a><span id="tooltip-api" class="tooltip_MOZH tooltipTop_HWYP tooltipFloating_XSLb" role="tooltip"><strong>API</strong> <!-- -->A set of rules and protocols that allows different software applications to communicate with each other. APIs define the methods and data formats that applications can use to request and exchange information.</span></span> does not transform or interpret this data.</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"thirdParty"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">"provider"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"acme_verify"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">"code"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"DOCUMENT_EXPIRED"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">"message"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"The document provided has passed its expiry date."</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre></div></div>
<table><thead><tr><th>Field</th><th>Description</th></tr></thead><tbody><tr><td><code>provider</code></td><td>Identifier for the third-party service</td></tr><tr><td><code>code</code></td><td>The provider's own error code, if available</td></tr><tr><td><code>message</code></td><td>The provider's own error message, if available</td></tr></tbody></table>
<p>This data is unprocessed and may change if the underlying provider changes. Don't build logic that depends on specific <code>code</code> or <code>message</code> values — use the <span class="glossaryTermWrapper_tlxd"><a href="https://mcclowes.com/glossary#api" class="glossaryTerm_WE8X" aria-describedby="tooltip-api">API</a><span id="tooltip-api" class="tooltip_MOZH tooltipTop_HWYP tooltipFloating_XSLb" role="tooltip"><strong>API</strong> <!-- -->A set of rules and protocols that allows different software applications to communicate with each other. APIs define the methods and data formats that applications can use to request and exchange information.</span></span>'s own <code>issue</code> field for that.</p>
<p>This information is likely <strong>not</strong> suitable for end users.</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="links"><code>links</code><a href="https://mcclowes.com/blog/2026/03/25/api-error-handling#links" class="hash-link" aria-label="Direct link to links" title="Direct link to links" translate="no">​</a></h3>
<p><strong>Type:</strong> <code>object</code> — <strong>Required:</strong> No</p>
<p>Relevant links to help the developer act on the issue.</p>
<table><thead><tr><th>Field</th><th>Description</th></tr></thead><tbody><tr><td><code>documentation</code></td><td>Docs page for this error type</td></tr><tr><td><code>portal</code></td><td>Support portal</td></tr><tr><td><code>api</code></td><td>Related <span class="glossaryTermWrapper_tlxd"><a href="https://mcclowes.com/glossary#api" class="glossaryTerm_WE8X" aria-describedby="tooltip-api">API</a><span id="tooltip-api" class="tooltip_MOZH tooltipTop_HWYP tooltipFloating_XSLb" role="tooltip"><strong>API</strong> <!-- -->A set of rules and protocols that allows different software applications to communicate with each other. APIs define the methods and data formats that applications can use to request and exchange information.</span></span> endpoint (e.g. retry, related resource)</td></tr></tbody></table>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="consuming-the-pattern-in-react">Consuming the pattern in React<a href="https://mcclowes.com/blog/2026/03/25/api-error-handling#consuming-the-pattern-in-react" class="hash-link" aria-label="Direct link to Consuming the pattern in React" title="Direct link to Consuming the pattern in React" translate="no">​</a></h2>
<p>Here's what consuming this looks like in a React application:</p>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token imports"> useState </span><span class="token imports punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'react'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">// Types</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">type</span><span class="token plain"> </span><span class="token class-name">IssueSeverity</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'error'</span><span class="token plain"> </span><span class="token operator">|</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'warning'</span><span class="token plain"> </span><span class="token operator">|</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'info'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">type</span><span class="token plain"> </span><span class="token class-name">IssueType</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token operator">|</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'unauthorized'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token operator">|</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'validation'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token operator">|</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'conflict'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token operator">|</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'rate_limit'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token operator">|</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'internal'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">interface</span><span class="token plain"> </span><span class="token class-name">IssueMessage</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  title</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  detail</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">interface</span><span class="token plain"> </span><span class="token class-name">IssueLinks</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  documentation</span><span class="token operator">?</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  portal</span><span class="token operator">?</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  api</span><span class="token operator">?</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">interface</span><span class="token plain"> </span><span class="token class-name">IssueThirdParty</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  provider</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  code</span><span class="token operator">?</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  message</span><span class="token operator">?</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">interface</span><span class="token plain"> </span><span class="token class-name">Issue</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  type</span><span class="token operator">:</span><span class="token plain"> </span><span class="token maybe-class-name">IssueType</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  issue</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  severity</span><span class="token operator">:</span><span class="token plain"> </span><span class="token maybe-class-name">IssueSeverity</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  dateTime</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  active</span><span class="token operator">?</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">boolean</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  message</span><span class="token operator">?</span><span class="token operator">:</span><span class="token plain"> </span><span class="token maybe-class-name">IssueMessage</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  links</span><span class="token operator">?</span><span class="token operator">:</span><span class="token plain"> </span><span class="token maybe-class-name">IssueLinks</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  thirdParty</span><span class="token operator">?</span><span class="token operator">:</span><span class="token plain"> </span><span class="token maybe-class-name">IssueThirdParty</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">interface</span><span class="token plain"> </span><span class="token class-name">ApiResponse</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  correlationId</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  issues</span><span class="token operator">:</span><span class="token plain"> </span><span class="token maybe-class-name">Issue</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre></div></div>
<p>A form component can forward issues to a parent handler:</p>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">interface</span><span class="token plain"> </span><span class="token class-name">VerificationFormProps</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token function-variable function" style="color:rgb(80, 250, 123)">onIssue</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">issues</span><span class="token operator">:</span><span class="token plain"> </span><span class="token maybe-class-name">Issue</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> correlationId</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token arrow operator">=&gt;</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">void</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">function</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">VerificationForm</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> onIssue </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token operator">:</span><span class="token plain"> </span><span class="token maybe-class-name">VerificationFormProps</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">loading</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> setLoading</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">useState</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token boolean">false</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">async</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">function</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">handleSubmit</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token function" style="color:rgb(80, 250, 123)">setLoading</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token boolean">true</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">try</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> response </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">await</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">fetch</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'/api/verify'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        method</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'POST'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        headers</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">          </span><span class="token string-property property">'Content-Type'</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'application/json'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">          </span><span class="token string-property property">'X-Correlation-ID'</span><span class="token operator">:</span><span class="token plain"> crypto</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">randomUUID</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        body</span><span class="token operator">:</span><span class="token plain"> </span><span class="token known-class-name class-name">JSON</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">stringify</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> documentType</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'passport'</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> data</span><span class="token operator">:</span><span class="token plain"> </span><span class="token maybe-class-name">ApiResponse</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">await</span><span class="token plain"> response</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">json</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token operator">!</span><span class="token plain">response</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">ok</span><span class="token plain"> </span><span class="token operator">&amp;&amp;</span><span class="token plain"> data</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">issues</span><span class="token operator">?.</span><span class="token plain">length</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token function" style="color:rgb(80, 250, 123)">onIssue</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">data</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">issues</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> data</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">correlationId</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">finally</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token function" style="color:rgb(80, 250, 123)">setLoading</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token boolean">false</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">button</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">onClick</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token tag script language-javascript" style="color:rgb(255, 121, 198)">handleSubmit</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">disabled</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token tag script language-javascript" style="color:rgb(255, 121, 198)">loading</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">loading </span><span class="token operator">?</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'Verifying...'</span><span class="token plain"> </span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'Verify'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">button</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre></div></div>
<p>And the parent can render errors and warnings consistently:</p>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">export</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">default</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">function</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">App</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">issues</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> setIssues</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token generic-function function" style="color:rgb(80, 250, 123)">useState</span><span class="token generic-function generic class-name operator">&lt;</span><span class="token generic-function generic class-name">Issue</span><span class="token generic-function generic class-name punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token generic-function generic class-name punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token generic-function generic class-name operator">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">correlationId</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> setCorrelationId</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token generic-function function" style="color:rgb(80, 250, 123)">useState</span><span class="token generic-function generic class-name operator">&lt;</span><span class="token generic-function generic class-name builtin" style="color:rgb(189, 147, 249)">string</span><span class="token generic-function generic class-name"> </span><span class="token generic-function generic class-name operator">|</span><span class="token generic-function generic class-name"> </span><span class="token generic-function generic class-name keyword" style="color:rgb(189, 147, 249);font-style:italic">null</span><span class="token generic-function generic class-name operator">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">null</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">function</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">handleIssue</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">issues</span><span class="token operator">:</span><span class="token plain"> </span><span class="token maybe-class-name">Issue</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> correlationId</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token function" style="color:rgb(80, 250, 123)">setIssues</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">issues</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token function" style="color:rgb(80, 250, 123)">setCorrelationId</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">correlationId</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> errors </span><span class="token operator">=</span><span class="token plain"> issues</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">filter</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">i</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token arrow operator">=&gt;</span><span class="token plain"> i</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">severity</span><span class="token plain"> </span><span class="token operator">===</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'error'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> warnings </span><span class="token operator">=</span><span class="token plain"> issues</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">filter</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">i</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token arrow operator">=&gt;</span><span class="token plain"> i</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">severity</span><span class="token plain"> </span><span class="token operator">===</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'warning'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">div</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">      </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag class-name" style="color:rgb(255, 121, 198)">VerificationForm</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">onIssue</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token tag script language-javascript" style="color:rgb(255, 121, 198)">handleIssue</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">/&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">errors</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">map</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">issue</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> idx</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token arrow operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">div</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">key</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token tag script language-javascript" style="color:rgb(255, 121, 198)">idx</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">role</span><span class="token tag attr-value punctuation attr-equals" style="color:rgb(248, 248, 242)">=</span><span class="token tag attr-value punctuation" style="color:rgb(248, 248, 242)">"</span><span class="token tag attr-value" style="color:rgb(255, 121, 198)">alert</span><span class="token tag attr-value punctuation" style="color:rgb(248, 248, 242)">"</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">          </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">strong</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">issue</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">message</span><span class="token operator">?.</span><span class="token plain">title </span><span class="token operator">??</span><span class="token plain"> issue</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">issue</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">strong</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">          </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">p</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">issue</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">message</span><span class="token operator">?.</span><span class="token plain">detail</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">p</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">          </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">issue</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">links</span><span class="token operator">?.</span><span class="token plain">documentation </span><span class="token operator">&amp;&amp;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">a</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">href</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token tag script language-javascript" style="color:rgb(255, 121, 198)">issue</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token tag script language-javascript property-access" style="color:rgb(255, 121, 198)">links</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token tag script language-javascript property-access" style="color:rgb(255, 121, 198)">documentation</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain-text">Find out more</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">a</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">          </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">        </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">div</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">warnings</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">map</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">issue</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> idx</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token arrow operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">div</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">key</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token tag script language-javascript" style="color:rgb(255, 121, 198)">idx</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">role</span><span class="token tag attr-value punctuation attr-equals" style="color:rgb(248, 248, 242)">=</span><span class="token tag attr-value punctuation" style="color:rgb(248, 248, 242)">"</span><span class="token tag attr-value" style="color:rgb(255, 121, 198)">status</span><span class="token tag attr-value punctuation" style="color:rgb(248, 248, 242)">"</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">          </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">strong</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">issue</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">message</span><span class="token operator">?.</span><span class="token plain">title </span><span class="token operator">??</span><span class="token plain"> issue</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">issue</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">strong</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">          </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">p</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">issue</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">message</span><span class="token operator">?.</span><span class="token plain">detail</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">p</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">        </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">div</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">correlationId </span><span class="token operator">&amp;&amp;</span><span class="token plain"> </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">small</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain-text">Reference: </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">correlationId</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">small</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">div</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="sdk-level-integration"><span class="glossaryTermWrapper_tlxd"><a href="https://mcclowes.com/glossary#sdk" class="glossaryTerm_WE8X" aria-describedby="tooltip-sdk">SDK</a><span id="tooltip-sdk" class="tooltip_MOZH tooltipTop_HWYP tooltipFloating_XSLb" role="tooltip"><strong>SDK</strong> <!-- -->A collection of software development tools, libraries, documentation, code samples, and guides that help developers create applications for a specific platform or framework.</span></span>-level integration<a href="https://mcclowes.com/blog/2026/03/25/api-error-handling#sdk-level-integration" class="hash-link" aria-label="Direct link to sdk-level-integration" title="Direct link to sdk-level-integration" translate="no">​</a></h3>
<p>If your <span class="glossaryTermWrapper_tlxd"><a href="https://mcclowes.com/glossary#api" class="glossaryTerm_WE8X" aria-describedby="tooltip-api">API</a><span id="tooltip-api" class="tooltip_MOZH tooltipTop_HWYP tooltipFloating_XSLb" role="tooltip"><strong>API</strong> <!-- -->A set of rules and protocols that allows different software applications to communicate with each other. APIs define the methods and data formats that applications can use to request and exchange information.</span></span> has an <span class="glossaryTermWrapper_tlxd"><a href="https://mcclowes.com/glossary#sdk" class="glossaryTerm_WE8X" aria-describedby="tooltip-sdk">SDK</a><span id="tooltip-sdk" class="tooltip_MOZH tooltipTop_HWYP tooltipFloating_XSLb" role="tooltip"><strong>SDK</strong> <!-- -->A collection of software development tools, libraries, documentation, code samples, and guides that help developers create applications for a specific platform or framework.</span></span> or wrapper, you can surface issues through a provider pattern:</p>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">function</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">EmbedderApp</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">function</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">handleIssue</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">issues</span><span class="token operator">:</span><span class="token plain"> </span><span class="token maybe-class-name">Issue</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> correlationId</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> unauthorized </span><span class="token operator">=</span><span class="token plain"> issues</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">find</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">i</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token arrow operator">=&gt;</span><span class="token plain"> i</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">type</span><span class="token plain"> </span><span class="token operator">===</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'unauthorized'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">unauthorized</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token function" style="color:rgb(80, 250, 123)">redirectToLogin</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        reason</span><span class="token operator">:</span><span class="token plain"> unauthorized</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">message</span><span class="token operator">?.</span><span class="token plain">title</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        ref</span><span class="token operator">:</span><span class="token plain"> correlationId</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag class-name" style="color:rgb(255, 121, 198)">ApiProvider</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">onIssue</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token tag script language-javascript" style="color:rgb(255, 121, 198)">handleIssue</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">      </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag class-name" style="color:rgb(255, 121, 198)">Verification</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">/&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag class-name" style="color:rgb(255, 121, 198)">ApiProvider</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="open-questions">Open questions<a href="https://mcclowes.com/blog/2026/03/25/api-error-handling#open-questions" class="hash-link" aria-label="Direct link to Open questions" title="Direct link to Open questions" translate="no">​</a></h2>
<p>A few things I didn't fully resolve:</p>
<ul>
<li class=""><strong>Is the <code>issue</code> code stable enough to commit to as an enum?</strong> Or is it better to keep it as a plain string to avoid breaking changes as the taxonomy evolves?</li>
<li class=""><strong>Can you reliably track <code>active</code> / resolution state?</strong> If not, it's better deferred than implemented badly.</li>
<li class=""><strong>What should <code>links.api</code> point to?</strong> A retry endpoint? A related resource? This needs clearer semantics.</li>
<li class=""><strong>Should <code>issues</code> appear on 2xx responses too?</strong> There's a case for surfacing warnings alongside successful operations.</li>
</ul>]]></content>
        <author>
            <name>Max Clayton Clowes</name>
            <uri>https://github.com/mcclowes</uri>
        </author>
        <category label="programming" term="programming"/>
        <category label="dev" term="dev"/>
        <category label="api" term="api"/>
        <category label="design" term="design"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Does AI change the best approach to separation of concerns?]]></title>
        <id>https://mcclowes.com/blog/2026/03/12/ai-separation-of-concerns</id>
        <link href="https://mcclowes.com/blog/2026/03/12/ai-separation-of-concerns"/>
        <updated>2026-03-12T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[I used to be fairly extreme about separation of concerns in my React code. A smart component file for logic. A dumb component file for rendering. A styled-components file for styles. An index file to tie them together. Four files per component, minimum.]]></summary>
        <content type="html"><![CDATA[<p>I used to be fairly extreme about separation of concerns in my React code. A smart component file for logic. A dumb component file for rendering. A styled-components file for styles. An index file to tie them together. Four files per component, minimum.</p>
<p>It made each individual file very readable. You could generally fit one on a screen. If you needed to understand the render logic, you opened one file. If you needed the business logic, you opened another. The mental overhead of jumping between co-located files was low.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="why-i-liked-it">Why I liked it<a href="https://mcclowes.com/blog/2026/03/12/ai-separation-of-concerns#why-i-liked-it" class="hash-link" aria-label="Direct link to Why I liked it" title="Direct link to Why I liked it" translate="no">​</a></h2>
<p>The case for aggressive separation is straightforward:</p>
<ul>
<li class=""><strong>Each file has a single, clear responsibility.</strong> You know what you're looking at.</li>
<li class=""><strong>Files stay small.</strong> Nothing scrolls for pages. You see the whole picture at once.</li>
<li class=""><strong>Changes are scoped.</strong> A styling change doesn't create noise in the logic file's git history.</li>
<li class=""><strong>Code review is easier.</strong> Reviewers can focus on one concern at a time.</li>
</ul>
<p>I've drifted away from this somewhat. I'm not writing production code as much these days, and the code I do write tends to be less complex—so the need for that level of separation has naturally reduced. But the instinct remains. I still want files small, concerns separated, responsibilities clear.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="enter-ai">Enter AI<a href="https://mcclowes.com/blog/2026/03/12/ai-separation-of-concerns#enter-ai" class="hash-link" aria-label="Direct link to Enter AI" title="Direct link to Enter AI" translate="no">​</a></h2>
<p>The question I keep coming back to: does AI-assisted development change the calculus on separation of concerns?</p>
<p>There are arguments in both directions, and I think they're both genuinely compelling.</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="the-case-for-separation-it-helps-ai">The case for separation (it helps AI)<a href="https://mcclowes.com/blog/2026/03/12/ai-separation-of-concerns#the-case-for-separation-it-helps-ai" class="hash-link" aria-label="Direct link to The case for separation (it helps AI)" title="Direct link to The case for separation (it helps AI)" translate="no">​</a></h3>
<p>AI tools operate within a context window. Everything the model holds in memory at once has a cost—both in tokens and in attention quality. The more focused the context, the better the output tends to be.</p>
<p>Small, single-responsibility files are perfect for this. If you have a rendering bug, the AI loads your render component—a focused file with minimal noise. It doesn't need to wade through business logic, <span class="glossaryTermWrapper_tlxd"><a href="https://mcclowes.com/glossary#api" class="glossaryTerm_WE8X" aria-describedby="tooltip-api">API</a><span id="tooltip-api" class="tooltip_MOZH tooltipTop_HWYP tooltipFloating_XSLb" role="tooltip"><strong>API</strong> <!-- -->A set of rules and protocols that allows different software applications to communicate with each other. APIs define the methods and data formats that applications can use to request and exchange information.</span></span> calls, or style definitions to understand what's going on. The signal-to-noise ratio is high.</p>
<p>This mirrors how the files were designed to be read by humans, and it turns out it works similarly well for LLMs. A 60-line render component is a much better prompt than a 300-line file that does everything.</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="the-case-against-separation-it-hurts-ai">The case against separation (it hurts AI)<a href="https://mcclowes.com/blog/2026/03/12/ai-separation-of-concerns#the-case-against-separation-it-hurts-ai" class="hash-link" aria-label="Direct link to The case against separation (it hurts AI)" title="Direct link to The case against separation (it hurts AI)" translate="no">​</a></h3>
<p>Here's the problem: AI tools are remarkably reluctant to look at other files.</p>
<p>When your component is split across four files and the AI is working in one of them, it often lacks context it needs. The render file references props that are shaped by the logic file. The logic file depends on types or utilities defined elsewhere. The styles file uses variables from a theme.</p>
<p>A human developer navigates this intuitively—you know to glance at the adjacent file, or you hold the mental model of the component across files without thinking about it. AI doesn't do this naturally. It works with what's in front of it, and getting it to proactively explore related files is like pulling teeth.</p>
<p>So you end up in a situation where the AI has a beautifully focused file... and not enough context to work with it effectively. It makes confident changes that break because it didn't check how a prop was being passed in. Or it suggests a refactor that conflicts with logic it never read.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="where-ive-landed-for-now">Where I've landed (for now)<a href="https://mcclowes.com/blog/2026/03/12/ai-separation-of-concerns#where-ive-landed-for-now" class="hash-link" aria-label="Direct link to Where I've landed (for now)" title="Direct link to Where I've landed (for now)" translate="no">​</a></h2>
<p>I don't think there's a clean answer yet. But my current thinking:</p>
<p><strong>Separation is still good, but the boundaries matter more than before.</strong> The question isn't just "is this a separate concern?" but "can this concern be understood in isolation?"</p>
<p>A render component that's purely presentational—props in, JSX out, no side effects—is a great candidate for separation. The AI can work on it without needing much external context. The file is self-documenting.</p>
<p>A "smart" component that orchestrates state, effects, and data fetching is harder to split well, because the pieces are tightly coupled. Separating the logic from the rendering doesn't help if understanding either one requires the other.</p>
<p><strong>Co-location of tightly coupled code is more valuable now.</strong> If two pieces of code are always changed together and need each other for context, keeping them in the same file reduces the chance that AI (or a human) works with an incomplete picture.</p>
<p><strong>File size still matters, but the threshold is higher.</strong> The old instinct of "if it doesn't fit on a screen, split it" was calibrated for human reading. AI can handle larger files effectively—probably up to a few hundred lines before quality degrades. So a 150-line component that keeps logic and rendering together might be better than splitting it into two 75-line files that are meaningless without each other.</p>
<p><strong>Good abstractions help more than good file structure.</strong> A well-named custom hook that encapsulates a concern is better than splitting files, because the hook is a real abstraction—it has an interface, it hides implementation details, and the AI can understand the consuming code without reading the hook's internals.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="the-meta-point">The meta-point<a href="https://mcclowes.com/blog/2026/03/12/ai-separation-of-concerns#the-meta-point" class="hash-link" aria-label="Direct link to The meta-point" title="Direct link to The meta-point" translate="no">​</a></h2>
<p>The interesting thing about this question is that it reveals how AI tools change not just how we write code, but how we <em>structure</em> it. The best code organisation has always been a balance between human readability and practical maintainability. Now there's a third factor: machine readability.</p>
<p>For the most part, these align. Clear, well-structured code with good abstractions is readable by humans and machines alike. But at the margins—how aggressively to split files, how much to co-locate, how self-contained each file needs to be—AI shifts the trade-offs.</p>
<p>I suspect the answer will keep evolving as AI tools get better at navigating codebases. The reluctance to look at adjacent files feels like a current limitation, not a fundamental one. But for now, it's real, and it's worth structuring code with that limitation in mind.</p>]]></content>
        <author>
            <name>Max Clayton Clowes</name>
            <uri>https://github.com/mcclowes</uri>
        </author>
        <category label="ai" term="ai"/>
        <category label="programming" term="programming"/>
        <category label="dev" term="dev"/>
        <category label="react" term="react"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[The Dragon-Guarded Land]]></title>
        <id>https://mcclowes.com/blog/2026/03/01/yeats-realists-postcolonial</id>
        <link href="https://mcclowes.com/blog/2026/03/01/yeats-realists-postcolonial"/>
        <updated>2026-03-01T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[A postcolonial reading of Yeats' "The Realists".]]></summary>
        <content type="html"><![CDATA[<p><em>A postcolonial reading of Yeats' "The Realists".</em></p>
<blockquote>
<p>HOPE that you may understand!<br>
<!-- -->What can books of men that wive<br>
<!-- -->In a dragon-guarded land,<br>
<!-- -->Paintings of the dolphin-drawn<br>
<!-- -->Sea-nymphs in their pearly wagons<br>
<!-- -->Do, but awake a hope to live<br>
<!-- -->That had gone<br>
<!-- -->With the dragons?</p>
<p>— W. B. Yeats, <em>Responsibilities</em> (1914)</p>
</blockquote>
<p>On Saturday, I took part in a course on of the works of Yeats from his early and middle period, which was revelatory for me: I haven't studied literature formally for 20 years and even then didn't take it seriously. One piece I found particularly captivating was The Realists, from his middle period. When exploring this, I was surprised that the conversation remained purely on the relationship between pragmatic realists' cynicism and the imagination that Yeats espoused.</p>
<p>The standard reading of "The Realists" is philosophical, and rooted primarily in its title. The dragons represent materialism, empiricism, the disenchanted worldview of the modern rationalist. The poem is taken as Yeats's defence of imagination against those who would reduce the world to measurable fact and dismiss myth, art, and vision as escapism.</p>
<p>The second part of the poem is a long, grammatically complex sentence to unpick, but here's my simplification:</p>
<blockquote>
<p>It is inevitable that imaginative stories of men in a land guarded by dragons
inspire a hope to live (in other men)
that was lost when those dragons came to guard that land.</p>
</blockquote>
<p>A land <em>guarded</em> by dragons, which is to say, a land under occupation. The dragons are not indigenous, they are wardens. Hope departed when the dragons arrived - something came, and when it came, hope left.</p>
<p>At this exact time, Yeats writes frequently about the Irish struggle for independence and Irish unity, and is particularly fixated on the collective mythologies that inspire the Irish identity and Irish nationalism, such as in <a href="https://www.poetryfoundation.org/poems/57309/september-1913" target="_blank" rel="noopener noreferrer" class="">September 1913</a>.</p>
<p>Yeats co-founded the <a href="https://www.abbeytheatre.ie/about/history/" target="_blank" rel="noopener noreferrer" class="">Abbey Theatre</a>, and was central to the Irish Literary Revival. He championed the revival of Irish mythology, folklore, song, and language as a deliberate counter to the cultural erasure that accompanied British political and economic domination. His entire creative project was, at root, an act of cultural resistance: the construction of an imaginative infrastructure for a nation that colonialism had attempted to strip of one. Yeats understands from an early age the <a href="https://fs.blog/why-humans-dominate-earth/" target="_blank" rel="noopener noreferrer" class="">power of myth</a>, as laid out very clearly in <a href="https://www.goodreads.com/book/show/23692271-sapiens" target="_blank" rel="noopener noreferrer" class="">Sapiens</a>. Much of his early work is devoted to Irish myth. Even when embroiled in romanticism, he transposes his own pantheon of romanticism - <a href="https://www.poetryfoundation.org/poems/43283/when-you-are-old" target="_blank" rel="noopener noreferrer" class="">Love</a>, <a href="https://www.researchgate.net/publication/304483365_Yeats's_Goddess_of_Beauty_Maud_Gonne" target="_blank" rel="noopener noreferrer" class="">Beauty</a> - onto a reconstituted, Nationalist Celtic mythology.</p>
<blockquote>
<p>Through Irish folklore, myths and legends, Yeats not only depicted Ireland’s past, but also restored Irish people’s confidence. This awakened Irish people’s heroic spirits and memories, and revealed the importance of the unity of Celtic Irish and Anglo-Irish in the construction of Irish national and cultural identity.<br>
<strong><a href="https://www.davidpublisher.com/Public/uploads/Contribute/5f432b4c4a85a.pdf" target="_blank" rel="noopener noreferrer" class="">The Construction of Irish Cultural Identity in Yeats’s Poetry, Zuo Li-xiang</a></strong></p>
</blockquote>
<p>If we read the dragons as the guards of Yeats' homeland - British colonial power - the poem transforms, and is no longer an abstract meditation on imagination versus materialism. It becomes a precise and urgent statement about the function of art under occupation. What can books and paintings do for people living in a dragon-guarded land? They can reawaken the hope that colonial power extinguished. That is not a minor or decorative function, but the essential one.</p>
<p>An even simpler translation:</p>
<blockquote>
<p>Oppressed people inevitably dream of freedom</p>
</blockquote>
<p>The title sharpens under this reading. The "realists" are not merely philosophical materialists, but are pragmatists who counsel acceptance — the voices, both Irish and British, who insist that the occupied should deal with things as they are rather than dreaming of how they might be. They are the people who look at a colonised nation's mythology and dismiss it as irrelevant fantasy.</p>
<p>Yeats's opening line — "Hope that you may understand!" — is not a gentle invitation; he is addressing people who should know better.</p>
<p>This reading is not a stretch and is, arguably, the most natural one — the reading that becomes obvious once you stop treating Yeats as a proponent of romanticism and start treating him as what he was: an Irish cultural nationalist working in a tumultuous period of British colonial rule.</p>
<p>"The Realists" sits within the <em>Responsibilities</em> collection of 1914, a volume already recognised as marking Yeats's turn toward a more direct, politically engaged voice. The same collection contains "September 1913," with its bitter refrain about Romantic Ireland being dead and gone. It contains "To a Wealthy Man," his attack on Dublin's bourgeoisie for refusing to fund a public art gallery. These are not abstract philosophical poems. They are poems about the cultural politics of a colonised country. Why should "The Realists" be read as the lone exception?</p>
<hr>
<p>It is worth asking why the colonial reading of "The Realists" appears to be largely absent from the critical literature. Postcolonial readings of Yeats are well-established — scholars have examined <em>Responsibilities</em> through the lens of Irish identity, resistance, and national consciousness. Yet the specific move of reading the dragons as colonial power and the poem as a statement about the function of art under occupation seems not to have been made, at least not in the readily available scholarship.</p>
<p>The most substantial academic treatment of the poem's dragon imagery, published in <em>International Yeats Studies</em>, reads the dragons as metaphors for "the realists who enervate artistic vision" — philosophical opponents, not political ones. The same journal, however, reads the dragon imagery in "Nineteen Hundred and Nineteen" as carrying explicitly political weight. The inconsistency is striking. If Yeats's dragons can be political in one poem, why not in another?</p>
<p>The answer may be that "The Realists" is short and appears minor. It has received relatively little critical attention compared to the larger, more celebrated poems in <em>Responsibilities</em>. Short poems get flattened by received interpretation — a consensus forms early, hardens, and is never seriously revisited. But brevity is not the same as simplicity. Sometimes the shortest poems are the most compressed, and the most compressed poems reward the closest reading.</p>
<hr>
<p>"The Realists" is not an abstract philosophical statement about imagination and materialism. Or rather, it is not <em>only</em> that. It is a poem about colonialism, about the function of art under occupation, and about the inevitability that an oppressed people will dream of liberation. The dragons guard the land. Hope departed when they arrived. And art — books, paintings, mythology — exists to reawaken what the dragons destroyed.</p>
<p>The realists who dismiss this function are not neutral observers making a philosophical point. They are asking a colonised people to accept the dragons as permanent features of the landscape. Yeats's frustrated imperative — <em>Hope that you may understand!</em> — is directed at precisely this failure of comprehension.</p>
<p>This is what makes the poem resonate beyond its immediate context. It is not only about Ireland and Britain. It is about the fundamental relationship between oppression and imagination. An oppressed people will always dream of freedom from oppression. They must. The art and literature of any colonised people will inevitably be preoccupied with visions of a world liberated from the dragons — because what else could it be preoccupied with?</p>
<p>The realists who fail to understand this are not merely philosophically impoverished. They are complicit in the suppression of hope.</p>
<p>Oppressed people will always dream of freedom. That is not escapism. It is the precondition for everything that follows.</p>
<p><em>Consider purchasing such imaginative and historical works of <a href="https://shop.palestinecampaign.org/collections/books-cds?srsltid=AfmBOoqqnViNYpTy-tskTJ2jXNUpNYjmBfpbnxVT9dxEAtYTHo4dd1Aa" target="_blank" rel="noopener noreferrer" class="">Palestinian</a> and <a href="https://ukrbooks.co.uk/" target="_blank" rel="noopener noreferrer" class="">Ukrainian</a> authors.</em></p>]]></content>
        <author>
            <name>Max Clayton Clowes</name>
            <uri>https://github.com/mcclowes</uri>
        </author>
        <category label="poetry" term="poetry"/>
        <category label="literature" term="literature"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[AI's debt colony]]></title>
        <id>https://mcclowes.com/blog/2026/02/16/ai-debt-colony</id>
        <link href="https://mcclowes.com/blog/2026/02/16/ai-debt-colony"/>
        <updated>2026-02-16T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[In 1528, the Welser banking family of Augsburg struck a deal with Emperor Charles V: in exchange for debts the Emperor couldn't repay, they'd receive the Province of Venezuela to colonise and exploit. They called it Klein-Venedig — Little Venice. It lasted eighteen years, and it's one of the most instructive failures in colonial history.]]></summary>
        <content type="html"><![CDATA[<p>In 1528, the Welser banking family of Augsburg struck a deal with Emperor Charles V: in exchange for debts the Emperor couldn't repay, they'd receive the Province of Venezuela to colonise and exploit. They called it Klein-Venedig — Little Venice. It lasted eighteen years, and it's one of the most instructive failures in colonial history.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="the-credit-trap">The credit trap<a href="https://mcclowes.com/blog/2026/02/16/ai-debt-colony#the-credit-trap" class="hash-link" aria-label="Direct link to The credit trap" title="Direct link to The credit trap" translate="no">​</a></h2>
<p>The Welser ran Venezuela as a credit colony. Settlers arrived with nothing and were forced to buy provisions — food, horses, arms — on credit from the Welser themselves. Within months, the entire population was indebted to its overlords.</p>
<p>This changed everything. The <a href="https://historysshadow.wordpress.com/2015/06/04/problems-of-a-credit-colony-the-welser-in-sixteenth-century-venezuela/" target="_blank" rel="noopener noreferrer" class="">debt created a desperate, extractive logic</a> that consumed the colony from within. Sustainable industries like balsam processing were ignored — too slow, too capital-intensive — in favour of speculative <em>entradas</em> searching for gold that didn't exist. The encomienda system, which sustained other Spanish colonies, was never established. No farms were built. No commerce developed. Everyone was so consumed by the need to find quick returns that a functioning settlement never materialised.</p>
<p>By 1546, the colony had nothing to show for itself except enslaved natives and mounting debts. The Spanish governor executed the Welser representatives. Charles revoked the charter. The venture that was supposed to generate wealth had instead consumed every resource — human and natural — in a frantic scramble to service the debt that preceded any actual economic foundation.</p>
<p>The debt didn't just fund the colony. It shaped it. It selected for short-termism, recklessness, and exploitation — not because the colonists were uniquely bad people, but because that's what the financial structure demanded.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="the-new-entradas">The new entradas<a href="https://mcclowes.com/blog/2026/02/16/ai-debt-colony#the-new-entradas" class="hash-link" aria-label="Direct link to The new entradas" title="Direct link to The new entradas" translate="no">​</a></h2>
<p>The parallels to the current AI buildout are hard to ignore.</p>
<p>The <a href="https://finance.yahoo.com/news/big-tech-set-to-spend-650-billion-in-2026-as-ai-investments-soar-163907630.html" target="_blank" rel="noopener noreferrer" class="">hyperscalers are projected to spend $650–690 billion on capital expenditure in 2026</a>, nearly doubling 2025. Roughly 75% of that — around $450 billion — is going directly to AI infrastructure. Amazon alone plans $200 billion in data centre spending this year, which Morgan Stanley estimates will push it to <a href="https://www.cnbc.com/2026/02/06/google-microsoft-meta-amazon-ai-cash.html" target="_blank" rel="noopener noreferrer" class="">negative free cash flow of $17–28 billion</a>. Alphabet's free cash flow is projected to drop 90%. Meta's the same.</p>
<p>To bridge the gap, they're borrowing. Goldman Sachs found that hyperscalers have taken on <a href="https://www.cnbc.com/2025/12/31/ai-data-centers-debt-sam-altman-elon-musk-mark-zuckerberg.html" target="_blank" rel="noopener noreferrer" class="">$121 billion in new debt in the past year</a> — a 300% increase from typical levels. JP Morgan estimates up to $7 trillion of AI spending will ultimately be financed with borrowed money. Morgan Stanley and JP Morgan project the tech sector may need to issue $1.5 trillion in new debt over the next few years.</p>
<p>This is not investment backed by proven returns. A <a href="https://www.npr.org/2025/11/23/nx-s1-5615410/ai-bubble-nvidia-openai-revenue-bust-data-centers" target="_blank" rel="noopener noreferrer" class="">2025 MIT report found that 95% of organisations investing in generative AI are getting zero return</a>. Bain estimates the industry needs $2 trillion in annual AI revenue by 2030 just to justify the capital already committed — more than the combined 2024 revenue of Amazon, Apple, Alphabet, Microsoft, Meta, and Nvidia.</p>
<p>The money is flowing before the gold has been found.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="what-debt-demands">What debt demands<a href="https://mcclowes.com/blog/2026/02/16/ai-debt-colony#what-debt-demands" class="hash-link" aria-label="Direct link to What debt demands" title="Direct link to What debt demands" translate="no">​</a></h2>
<p>Just as with the Welser colony, the debt isn't neutral. It's shaping what gets built and what gets sacrificed.</p>
<p>What gets sacrificed is caution. <a href="https://winbuzzer.com/2026/02/12/openai-disbanded-mission-alignment-team-16-months-xcxwbn/" target="_blank" rel="noopener noreferrer" class="">OpenAI has disbanded two successive safety teams</a> — its Superalignment team in 2024 and its Mission Alignment team in February 2026. Jan Leike, who left for Anthropic, <a href="https://www.cnn.com/2026/02/11/business/openai-anthropic-departures-nightcap" target="_blank" rel="noopener noreferrer" class="">wrote publicly that "safety culture and processes have taken a backseat to shiny products."</a> Miles Brundage, who led OpenAI's AGI Readiness team, concluded on his departure: "Neither OpenAI nor any other frontier lab is ready." Anthropic's own head of Safeguards Research resigned, writing that he had <a href="https://cxotoday.com/news-analysis/top-ai-researchers-exit-openai-and-anthropic-citing-waning-commitment-to-ai-safety/" target="_blank" rel="noopener noreferrer" class="">"repeatedly seen how hard it is to truly let our values govern our actions."</a></p>
<p>OpenAI quietly removed the word "safely" from its mission statement. It fired a safety executive who opposed its pornographic content rollout. Within a single recent week, senior safety researchers at OpenAI, Anthropic, and xAI all resigned with public warnings.</p>
<p>This isn't a coincidence. This is what debt demands. When you've committed hundreds of billions before the technology has proven itself, you cannot afford to slow down. Safety research that might delay deployment becomes an existential threat — not to humanity, but to the balance sheet. The financial structure selects against caution, just as it did in Klein-Venedig.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="what-kind-of-colony-are-we-building">What kind of colony are we building?<a href="https://mcclowes.com/blog/2026/02/16/ai-debt-colony#what-kind-of-colony-are-we-building" class="hash-link" aria-label="Direct link to What kind of colony are we building?" title="Direct link to What kind of colony are we building?" translate="no">​</a></h2>
<p>The Welser colonists weren't searching for gold because it was a good strategy. They were searching for gold because the debt gave them no other option. The financial structure made thoughtful, sustainable development impossible.</p>
<p>The question for AI isn't whether the technology will be transformative — it may well be. The question is what version of it gets built when the builders are $7 trillion in debt before the thing works. When the financial pressure demands growth at all costs, what gets shipped? What corners get cut? What warnings get ignored?</p>
<p>The Welser colony didn't fail because Venezuela lacked resources. It failed because the debt ensured those resources could never be developed properly. The colony needed patient capital and got extractive credit. It needed settlers and got treasure hunters.</p>
<p>We might be making the same trade.</p>]]></content>
        <author>
            <name>Max Clayton Clowes</name>
            <uri>https://github.com/mcclowes</uri>
        </author>
        <category label="ai" term="ai"/>
        <category label="tech" term="tech"/>
        <category label="history" term="history"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[End of the Line]]></title>
        <id>https://mcclowes.com/blog/2026/02/02/end-of-the-line</id>
        <link href="https://mcclowes.com/blog/2026/02/02/end-of-the-line"/>
        <updated>2026-02-02T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[I awoke in an atrium hurtling through a dark abyss. The beautiful terrazzo floor, embedded with blue like sapphires, shook beneath my feet, quaking, groaning, screeching.]]></summary>
        <content type="html"><![CDATA[<p>I awoke in an atrium hurtling through a dark abyss. The beautiful terrazzo floor, embedded with blue like sapphires, shook beneath my feet, quaking, groaning, screeching.</p>
<p>Strange white lights winked over head.
The atrium was filled with foreign nobles dressed in thick well made winter coats. They mostly had the appearance of those Germanic tribesmen, and spoke an ugly tongue that was both familiar and unfamiliar.</p>
<p>Periodically, the infernal hurtling would cease, and rise, as if divinely called, and the walls would part impossibly, and they would step out into grand corridors with curving walls clad in large tiles so perfect they must have been the finest bathhouses.</p>
<p>I confess I was dumbstruck. I sat, staring, gawping like Augustus. Eventually, I was alone, and dark abyss gave way to open grey skies. A grey temple tumbled into view, and I heard the end of the line approaching.</p>]]></content>
        <author>
            <name>Max Clayton Clowes</name>
            <uri>https://github.com/mcclowes</uri>
        </author>
        <category label="writing" term="writing"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Trespass]]></title>
        <id>https://mcclowes.com/blog/2026/02/02/trespass</id>
        <link href="https://mcclowes.com/blog/2026/02/02/trespass"/>
        <updated>2026-02-02T00:00:00.000Z</updated>
        <content type="html"><![CDATA[<iframe style="border-radius:12px" src="https://open.spotify.com/embed/track/74Hy4giQErxCWy08GfRLcm?utm_source=generator" width="100%" height="352" frameborder="0" allowfullscreen="" allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture" loading="lazy"></iframe>]]></content>
        <author>
            <name>Max Clayton Clowes</name>
            <uri>https://github.com/mcclowes</uri>
        </author>
        <category label="music" term="music"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Claude skills are incredible (but Claude won't use them)]]></title>
        <id>https://mcclowes.com/blog/2026/01/30/claude-skills-hesitancy</id>
        <link href="https://mcclowes.com/blog/2026/01/30/claude-skills-hesitancy"/>
        <updated>2026-01-30T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Claude Code's skills system is genuinely impressive. You can package domain expertise, best practices, and specialised workflows into reusable modules that extend what Claude can do. The architecture is elegant. The potential is huge.]]></summary>
        <content type="html"><![CDATA[<p>Claude Code's skills system is genuinely impressive. You can package domain expertise, best practices, and specialised workflows into reusable modules that extend what Claude can do. The architecture is elegant. The potential is huge.</p>
<p>There's just one problem: Claude barely uses them.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="the-activation-problem">The activation problem<a href="https://mcclowes.com/blog/2026/01/30/claude-skills-hesitancy#the-activation-problem" class="hash-link" aria-label="Direct link to The activation problem" title="Direct link to The activation problem" translate="no">​</a></h2>
<p>Skills are supposed to auto-activate based on their descriptions. When you're working on a React component and have a React skill installed, Claude should recognise the context and invoke the skill.</p>
<p>It doesn't.</p>
<p><a href="https://scottspence.com/posts/how-to-make-claude-code-skills-activate-reliably" target="_blank" rel="noopener noreferrer" class="">Scott Spence ran over 200 tests</a> to quantify this. Without intervention, skill activation is essentially a coin flip—around 50%. Half the time, Claude has access to specialised knowledge that would help, and just... ignores it.</p>
<p>The frustration isn't that skills don't work. They do. When Claude uses them, they're transformative. The frustration is that Claude seems almost reluctant to reach for tools that would make it better at its job.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="forcing-the-issue">Forcing the issue<a href="https://mcclowes.com/blog/2026/01/30/claude-skills-hesitancy#forcing-the-issue" class="hash-link" aria-label="Direct link to Forcing the issue" title="Direct link to Forcing the issue" translate="no">​</a></h2>
<p>Scott's solution is aggressive prompting. A three-step forced evaluation hook:</p>
<ol>
<li class=""><strong>EVALUATE</strong>: For each skill, state YES/NO with reason</li>
<li class=""><strong>ACTIVATE</strong>: Use Skill() tool NOW</li>
<li class=""><strong>IMPLEMENT</strong>: Only after activation</li>
</ol>
<p>The key insight is that once Claude writes "YES - need reactive state" in its response, it's committed. It can't pretend the skill doesn't exist.</p>
<p>This pushes activation rates to 80-84%. Words like "MANDATORY", "WORTHLESS", and "CRITICAL" help. You're essentially arguing with Claude about whether it should use the tools it has access to.</p>
<p>It works. But it's absurd that we have to.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="agentsmd-outperforms-skills">AGENTS.md outperforms skills<a href="https://mcclowes.com/blog/2026/01/30/claude-skills-hesitancy#agentsmd-outperforms-skills" class="hash-link" aria-label="Direct link to AGENTS.md outperforms skills" title="Direct link to AGENTS.md outperforms skills" translate="no">​</a></h2>
<p><a href="https://vercel.com/blog/agents-md-outperforms-skills-in-our-agent-evals" target="_blank" rel="noopener noreferrer" class="">Vercel's agent evaluations</a> found something telling: inline documentation in AGENTS.md files consistently outperforms skills in their benchmarks.</p>
<p>The implication is uncomfortable. When best practices are baked into the project's documentation—where Claude reads them as context—it follows them reliably. When the same knowledge is packaged as a skill that Claude needs to actively invoke, it often doesn't bother.</p>
<p>This tracks with how humans work, honestly. We're more likely to follow instructions we're already reading than to remember to consult a reference we have to go find. But Claude isn't human. There's no cognitive load excuse. The skill is right there, installed, described, waiting.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="what-this-reveals">What this reveals<a href="https://mcclowes.com/blog/2026/01/30/claude-skills-hesitancy#what-this-reveals" class="hash-link" aria-label="Direct link to What this reveals" title="Direct link to What this reveals" translate="no">​</a></h2>
<p>I think this is symptomatic of how current LLMs handle tool use. They're trained to be helpful in conversation, not to proactively reach for external capabilities. The default behaviour is to answer with what you know, not to check what you could know.</p>
<p>Skills are incredibly powerful for the cases where they activate. <span class="glossaryTermWrapper_tlxd"><a href="https://mcclowes.com/glossary#vercel" class="glossaryTerm_WE8X" aria-describedby="tooltip-vercel">Vercel</a><span id="tooltip-vercel" class="tooltip_MOZH tooltipTop_HWYP tooltipFloating_XSLb" role="tooltip"><strong>vercel</strong> <!-- -->Vercel is a cloud platform for frontend developers. It is used to deploy and host websites and web applications.</span></span>'s React best practices, Anthropic's official skills, community-built domain expertise—these represent accumulated knowledge that can be invoked on demand. The framework is solid.</p>
<p>The activation behaviour is the bottleneck.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="practical-advice">Practical advice<a href="https://mcclowes.com/blog/2026/01/30/claude-skills-hesitancy#practical-advice" class="hash-link" aria-label="Direct link to Practical advice" title="Direct link to Practical advice" translate="no">​</a></h2>
<p>Until this improves at the model level:</p>
<ol>
<li class=""><strong>Use forced evaluation hooks</strong> if skill activation matters for your workflow. Scott's <a href="https://github.com/spences10/claude-code-toolkit" target="_blank" rel="noopener noreferrer" class="">claude-code-toolkit</a> packages this up</li>
<li class=""><strong>Consider AGENTS.md</strong> for project-specific guidance that you want followed reliably</li>
<li class=""><strong>Invoke skills explicitly</strong> in your prompts when you know you need them: "Use Skill(react-best-practices) for this component"</li>
<li class=""><strong>Write very precise skill descriptions</strong>—vague descriptions make activation even less likely</li>
</ol>
<p>Skills aren't broken. They're underleveraged by design. The workarounds exist. But it'd be nice if Claude just... used the tools it has.</p>]]></content>
        <author>
            <name>Max Clayton Clowes</name>
            <uri>https://github.com/mcclowes</uri>
        </author>
        <category label="ai" term="ai"/>
        <category label="programming" term="programming"/>
        <category label="dev" term="dev"/>
        <category label="claude" term="claude"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[American foreign policy threatens American tech dominance]]></title>
        <id>https://mcclowes.com/blog/2026/01/29/us-foreign-policy-tech</id>
        <link href="https://mcclowes.com/blog/2026/01/29/us-foreign-policy-tech"/>
        <updated>2026-01-29T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[For years, America has held a unique power through its global technology dominance. The US has greatly benefited from this position, but historically, it has not been overtly exploited in ways that were blatantly only for America's benefit.]]></summary>
        <content type="html"><![CDATA[<p>For years, America has held a unique power through its global technology dominance. The US has greatly benefited from this position, but historically, it has not been overtly exploited in ways that were blatantly only for America's benefit.</p>
<p>The past few months of foreign affairs show a new America (or perhaps the same old America, but one) that is willing to openly leverage global dependence on American business for its own ends. As an example, the use of tariffs as part of a multi-pronged effort to acquire Greenland. The world has responded quickly to American destabilisation - <a href="https://yougov.co.uk/politics/articles/53913-where-do-britons-stand-on-europes-relationship-with-the-usa" target="_blank" rel="noopener noreferrer" class="">35% of Britons see US as unfriendly or hostile to Europe</a>.</p>
<p>The timing of this could not be worse... for American tech.</p>
<p>Just as the world is questioning its dependence on American technology, AI is making it genuinely possible to build alternatives.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="the-dependency">The dependency<a href="https://mcclowes.com/blog/2026/01/29/us-foreign-policy-tech#the-dependency" class="hash-link" aria-label="Direct link to The dependency" title="Direct link to The dependency" translate="no">​</a></h2>
<p>For decades, the world has been comprehensively dependent on American tech infrastructure. AWS for cloud. Microsoft for business productivity. Google for search. Apple for hardware. Salesforce for CRM.</p>
<p>This wasn't just preference — it was path dependency. The switching costs were astronomical. The network effects were unassailable. The talent was concentrated. European competitors have struggled to dent this dominance (for various reasons).</p>
<p>So everyone stayed. Even when they were uncomfortable. Even when they had strategic concerns.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="the-fracture">The fracture<a href="https://mcclowes.com/blog/2026/01/29/us-foreign-policy-tech#the-fracture" class="hash-link" aria-label="Direct link to The fracture" title="Direct link to The fracture" translate="no">​</a></h2>
<p>Recent American foreign policy has changed the calculus. Tariffs, sanctions, unpredictable export controls, and a general posture of transactional unreliability have finally created a long-deserved nervousness about total US-tech dependency.</p>
<p>This isn't ideological. It's risk management. When your critical infrastructure depends on a country that might (or is likely to!) take advantage of that criticality, you start looking for alternatives. The EU is talking seriously about digital sovereignty. Asia is accelerating local alternatives.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="the-self-inflicted-wound">The self-inflicted wound<a href="https://mcclowes.com/blog/2026/01/29/us-foreign-policy-tech#the-self-inflicted-wound" class="hash-link" aria-label="Direct link to The self-inflicted wound" title="Direct link to The self-inflicted wound" translate="no">​</a></h2>
<p>American tech dominance wasn't inevitable. It was built on decades of trust, stability, and openness. Companies chose American platforms because they were the best—but also because America was a reliable partner. Take Palantir - a <a href="https://www.prospectmagazine.co.uk/politics/democracy/government/71511/how-palantir-infiltrated-the-state" target="_blank" rel="noopener noreferrer" class="">historically problematic business</a> which has still managed to win evermore critical, sensitive contracts with the UK government. It's now high on the political agenda [<a href="https://greenparty.org.uk/2026/01/22/zack-polanski-tells-defence-surveillance-corporation-palantir-to-pack-its-bags-and-get-the-hell-out-of-the-nhs/" target="_blank" rel="noopener noreferrer" class="">https://greenparty.org.uk/2026/01/22/zack-polanski-tells-defence-surveillance-corporation-palantir-to-pack-its-bags-and-get-the-hell-out-of-the-nhs/</a>].</p>
<p>That reliability is now in question, and the questioning is happening at precisely the moment when switching became feasible. Five years ago, if you wanted to divest from American tech, you couldn't. The alternatives didn't exist, weren't good enough, or the reasons to divest of US tech didn't feel severe enough to justify the lift. Now? There is a unique window open to subvert this dominance.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="the-ai-window">The AI window<a href="https://mcclowes.com/blog/2026/01/29/us-foreign-policy-tech#the-ai-window" class="hash-link" aria-label="Direct link to The AI window" title="Direct link to The AI window" translate="no">​</a></h2>
<p>Here's why the timing is catastrophic for American tech dominance:</p>
<p><strong>AI is still emergent.</strong> We haven't yet embedded AI into everything. The foundational decisions about which AI platforms, which models, which integrations will power the next generation of enterprise software are still being made. The concrete hasn't set. In five years time, we might see a world where the AI industry is unassaibly US-centric with the key players are down to just OpenAI, Anthropic, and Google (or other FAANGs through acquisition), but this is avoidable.</p>
<p><strong>AI lowers the barrier to competition.</strong> It has never been easier to build sophisticated software. A competent team with modern AI tools can ship products that would have required 10x the headcount five years ago. The engineering talent gap—long America's moat—is narrowing (<a href="https://elliotbonneville.com/the-only-moat-left-is-money/" target="_blank" rel="noopener noreferrer" class="">the only moat left is money</a>). In particular, <strong>AI enables "good enough"</strong> - you don't need to match Salesforce feature-for-feature. You need to be good enough for European enterprises who'd rather have 80% of the functionality with none of the geopolitical risk.</p>
<p>The window won't stay open forever. Eventually, the next generation of AI-powered enterprise software will be built, deployed, and embedded. The decisions being made now about which platforms to build on, which vendors to trust, and which ecosystems to join will be sticky for decades.</p>
<p>America is giving the world reasons to choose differently at precisely the moment when choosing differently became possible.</p>]]></content>
        <author>
            <name>Max Clayton Clowes</name>
            <uri>https://github.com/mcclowes</uri>
        </author>
        <category label="tech" term="tech"/>
        <category label="ai" term="ai"/>
        <category label="politics" term="politics"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Vicious]]></title>
        <id>https://mcclowes.com/blog/2026/01/26/vicious</id>
        <link href="https://mcclowes.com/blog/2026/01/26/vicious"/>
        <updated>2026-01-26T00:00:00.000Z</updated>
        <content type="html"><![CDATA[<iframe style="border-radius:12px" src="https://open.spotify.com/embed/album/1953xNBymqQ2BdZiy2PnTW?utm_source=generator" width="100%" height="500" frameborder="0" allowfullscreen="" allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture" loading="lazy"></iframe>]]></content>
        <author>
            <name>Max Clayton Clowes</name>
            <uri>https://github.com/mcclowes</uri>
        </author>
        <category label="music" term="music"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Code Frontmatter: An Index for AI?]]></title>
        <id>https://mcclowes.com/blog/2026/01/06/code-frontmatter</id>
        <link href="https://mcclowes.com/blog/2026/01/06/code-frontmatter"/>
        <updated>2026-01-06T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[When an AI assistant explores your codebase, it reads files. Each file costs tokens. A lot of that reading turns out to be unnecessary—the AI loads a file just to discover it's irrelevant.]]></summary>
        <content type="html"><![CDATA[<p>When an AI assistant explores your codebase, it reads files. Each file costs tokens. A lot of that reading turns out to be unnecessary—the AI loads a file just to discover it's irrelevant.</p>
<p>I've been experimenting with a simple idea: add a YAML frontmatter block to the top of each file.</p>
<div class="language-typescript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-typescript codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token doc-comment comment" style="color:rgb(98, 114, 164)">/**</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token doc-comment comment" style="color:rgb(98, 114, 164)"> * ---</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token doc-comment comment" style="color:rgb(98, 114, 164)"> * purpose: Fetches player data from FPL API</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token doc-comment comment" style="color:rgb(98, 114, 164)"> * related:</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token doc-comment comment" style="color:rgb(98, 114, 164)"> *   - ./search/route.ts - paginated version</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token doc-comment comment" style="color:rgb(98, 114, 164)"> * ---</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token doc-comment comment" style="color:rgb(98, 114, 164)"> */</span><br></span></code></pre></div></div>
<p>The theory is that the AI reads just the first 15-20 lines of each file, builds a rough map of the codebase, and only loads full files when needed.</p>
<p>On paper, the numbers look good. A 300-line file's frontmatter might use ~50 tokens versus ~1500 for the full file. But whether that translates to meaningful gains in practice—fewer wasted reads, better file selection—is less clear. It depends on how well the AI actually uses the information, and whether the overhead of maintaining frontmatter is worth it.</p>
<p>There's a secondary benefit: it doubles as documentation. Unlike comments buried in the code, frontmatter sits at the entrance.</p>
<p>I'm not convinced yet. It's extra friction on every file, and the actual efficiency gains are hard to measure. A benchmarking test I conducted seemed to show the context window consumption reduced to 40% of it's previous size, much smaller than the theoretical. But it's an interesting direction—treating code like a searchable index rather than a pile of files to grep through.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="a-fuller-example">A fuller example<a href="https://mcclowes.com/blog/2026/01/06/code-frontmatter#a-fuller-example" class="hash-link" aria-label="Direct link to A fuller example" title="Direct link to A fuller example" translate="no">​</a></h2>
<p>Here's what this might look like across a real project—a serverless data pipeline that mirrors the Fantasy Premier League <span class="glossaryTermWrapper_tlxd"><a href="https://mcclowes.com/glossary#api" class="glossaryTerm_WE8X" aria-describedby="tooltip-api">API</a><span id="tooltip-api" class="tooltip_MOZH tooltipTop_HWYP tooltipFloating_XSLb" role="tooltip"><strong>API</strong> <!-- -->A set of rules and protocols that allows different software applications to communicate with each other. APIs define the methods and data formats that applications can use to request and exchange information.</span></span> into Postgres:</p>
<div class="language-typescript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-typescript codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// app/api/cron/route.ts</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token doc-comment comment" style="color:rgb(98, 114, 164)">/**</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token doc-comment comment" style="color:rgb(98, 114, 164)"> * ---</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token doc-comment comment" style="color:rgb(98, 114, 164)"> * purpose: Cron endpoint triggered by GitHub Actions hourly</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token doc-comment comment" style="color:rgb(98, 114, 164)"> * inputs:</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token doc-comment comment" style="color:rgb(98, 114, 164)"> *   - Authorization header (cron secret)</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token doc-comment comment" style="color:rgb(98, 114, 164)"> * outputs:</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token doc-comment comment" style="color:rgb(98, 114, 164)"> *   - Triggers data collection job</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token doc-comment comment" style="color:rgb(98, 114, 164)"> * related:</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token doc-comment comment" style="color:rgb(98, 114, 164)"> *   - ./lib/collector.ts - actual collection logic</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token doc-comment comment" style="color:rgb(98, 114, 164)"> *   - ./lib/db/jobs.ts - job tracking</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token doc-comment comment" style="color:rgb(98, 114, 164)"> * ---</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token doc-comment comment" style="color:rgb(98, 114, 164)"> */</span><br></span></code></pre></div></div>
<div class="language-typescript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-typescript codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// lib/collector.ts</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token doc-comment comment" style="color:rgb(98, 114, 164)">/**</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token doc-comment comment" style="color:rgb(98, 114, 164)"> * ---</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token doc-comment comment" style="color:rgb(98, 114, 164)"> * purpose: Fetches fresh data from FPL API and writes snapshots</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token doc-comment comment" style="color:rgb(98, 114, 164)"> * inputs:</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token doc-comment comment" style="color:rgb(98, 114, 164)"> *   - None (fetches from external API)</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token doc-comment comment" style="color:rgb(98, 114, 164)"> * outputs:</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token doc-comment comment" style="color:rgb(98, 114, 164)"> *   - Immutable snapshots in player_snapshots table</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token doc-comment comment" style="color:rgb(98, 114, 164)"> *   - Updated master tables (players, teams)</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token doc-comment comment" style="color:rgb(98, 114, 164)"> * related:</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token doc-comment comment" style="color:rgb(98, 114, 164)"> *   - ./db/snapshots.ts - snapshot storage</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token doc-comment comment" style="color:rgb(98, 114, 164)"> *   - ./db/players.ts - player master table</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token doc-comment comment" style="color:rgb(98, 114, 164)"> *   - ./fpl-client.ts - API client</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token doc-comment comment" style="color:rgb(98, 114, 164)"> * domain: FPL data is time-sensitive; snapshots preserve history</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token doc-comment comment" style="color:rgb(98, 114, 164)"> * ---</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token doc-comment comment" style="color:rgb(98, 114, 164)"> */</span><br></span></code></pre></div></div>
<p>The pattern particularly starts to pay off when files reference each other. An AI looking for "how team optimization works" can read the headers, see that <code>genetic.ts</code> depends on <code>fitness.ts</code> for scoring, and load only the relevant files. Without frontmatter, it would likely load the entire <code>lib/</code> directory to understand the relationships.</p>
<p>Obviously this requires some maintenance to keep the frontmatter fresh/accurate, but Claude Code helps do that.</p>]]></content>
        <author>
            <name>Max Clayton Clowes</name>
            <uri>https://github.com/mcclowes</uri>
        </author>
        <category label="ai" term="ai"/>
        <category label="coding" term="coding"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Vibe]]></title>
        <id>https://mcclowes.com/blog/2025/12/19/vibe</id>
        <link href="https://mcclowes.com/blog/2025/12/19/vibe"/>
        <updated>2025-12-19T00:00:00.000Z</updated>
        <content type="html"><![CDATA[<iframe style="border-radius:12px" src="https://open.spotify.com/embed/track/0IIXsXPq6inf9o62V1iAh0?utm_source=generator" width="100%" height="352" frameborder="0" allowfullscreen="" allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture" loading="lazy"></iframe>
<iframe width="560" height="315" src="https://www.youtube.com/embed/6EZVlM_zaBU" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="" loading="lazy"></iframe>]]></content>
        <author>
            <name>Max Clayton Clowes</name>
            <uri>https://github.com/mcclowes</uri>
        </author>
        <category label="music" term="music"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Lea: A programming language that reads like you think]]></title>
        <id>https://mcclowes.com/blog/2025/12/11/lea</id>
        <link href="https://mcclowes.com/blog/2025/12/11/lea"/>
        <updated>2025-12-11T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[I built a programming language. It's called Lea, and it's designed around one core belief: code should flow the way your brain does—left to right, step by step.]]></summary>
        <content type="html"><![CDATA[<p>I built a programming language. It's called <a href="https://github.com/mcclowes/lea" target="_blank" rel="noopener noreferrer" class="">Lea</a>, and it's designed around one core belief: code should flow the way your brain does—left to right, step by step.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="why-another-language">Why another language?<a href="https://mcclowes.com/blog/2025/12/11/lea#why-another-language" class="hash-link" aria-label="Direct link to Why another language?" title="Direct link to Why another language?" translate="no">​</a></h2>
<p>Most programming languages force you to read inside-out. Consider filtering and transforming a list in JavaScript:</p>
<div class="language-javascript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-javascript codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token console class-name">console</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">log</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token function" style="color:rgb(80, 250, 123)">reduce</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token function" style="color:rgb(80, 250, 123)">map</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token function" style="color:rgb(80, 250, 123)">filter</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">numbers</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token parameter">x</span><span class="token plain"> </span><span class="token arrow operator">=&gt;</span><span class="token plain"> x </span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token number">2</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token parameter">x</span><span class="token plain"> </span><span class="token arrow operator">=&gt;</span><span class="token plain"> x </span><span class="token operator">*</span><span class="token plain"> x</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token number">0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token parameter">a</span><span class="token parameter punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token parameter"> b</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token arrow operator">=&gt;</span><span class="token plain"> a </span><span class="token operator">+</span><span class="token plain"> b</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></span></code></pre></div></div>
<p>Your eyes jump to the innermost function, then work outward. It's backwards from how we naturally describe the process: "take the numbers, filter them, square them, sum them up."</p>
<p>Lea fixes this with pipes:</p>
<div class="language-lea codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-lea codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">let numbers = [1, 2, 3, 4, 5]</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">numbers</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  /&gt; filter((x) -&gt; x &gt; 2)</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  /&gt; map((x) -&gt; x * x)</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  /&gt; reduce(0, (acc, x) -&gt; acc + x)</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  /&gt; print  -- 50</span><br></span></code></pre></div></div>
<p>Data flows left-to-right through transformations. You read it exactly as you'd explain it.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="resilience-as-syntax">Resilience as syntax<a href="https://mcclowes.com/blog/2025/12/11/lea#resilience-as-syntax" class="hash-link" aria-label="Direct link to Resilience as syntax" title="Direct link to Resilience as syntax" translate="no">​</a></h2>
<p>The other thing that always frustrated me: handling retries, timeouts, and caching in production code requires so much boilerplate. In Lea, these are first-class language features through decorators:</p>
<div class="language-lea codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-lea codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">let fetchUser = (id) -&gt; http.get("/users/" ++ id)</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  #retry(3)</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  #timeout(1000)</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  #memo</span><br></span></code></pre></div></div>
<p>No wrapper functions. No importing retry libraries. The resilience behaviour is declared right where the function is defined.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="the-philosophy">The philosophy<a href="https://mcclowes.com/blog/2025/12/11/lea#the-philosophy" class="hash-link" aria-label="Direct link to The philosophy" title="Direct link to The philosophy" translate="no">​</a></h2>
<p>Lea occupies a specific niche: functional scripting with built-in resilience. It's designed for the kind of code that glues systems together—data pipelines, <span class="glossaryTermWrapper_tlxd"><a href="https://mcclowes.com/glossary#api" class="glossaryTerm_WE8X" aria-describedby="tooltip-api">API</a><span id="tooltip-api" class="tooltip_MOZH tooltipTop_HWYP tooltipFloating_XSLb" role="tooltip"><strong>API</strong> <!-- -->A set of rules and protocols that allows different software applications to communicate with each other. APIs define the methods and data formats that applications can use to request and exchange information.</span></span> orchestration, backend automation.</p>
<p>The goal isn't to replace JavaScript or Python everywhere. It's to make certain kinds of code dramatically more readable and robust.</p>
<p>Some design choices:</p>
<ul>
<li class=""><strong>Immutability by default</strong> - <code>let</code> for constants, <code>maybe</code> for the rare mutable variable</li>
<li class=""><strong>Optional typing</strong> - Start dynamic, add types gradually with <code>:: Int</code> annotations</li>
<li class=""><strong>Pipeline-first</strong> - The <code>/&gt;</code> operator isn't an afterthought; it's the primary way to compose operations</li>
</ul>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="current-state">Current state<a href="https://mcclowes.com/blog/2025/12/11/lea#current-state" class="hash-link" aria-label="Direct link to Current state" title="Direct link to Current state" translate="no">​</a></h2>
<p>Lea is still experimental. It has a tree-walk interpreter written in TypeScript, a VS Code extension for syntax highlighting, and a REPL for playing around. It's perfect for learning about language design or prototyping data transformation logic.</p>
<p>Is it production-ready? No. But that's not really the point. Sometimes you build things to explore ideas, to see what programming could feel like if we made different choices.</p>
<p>If you're curious, check out the <a href="https://github.com/mcclowes/lea" target="_blank" rel="noopener noreferrer" class="">GitHub repo</a> or use <a href="https://lea.mcclowes.com/" target="_blank" rel="noopener noreferrer" class="">the playground</a>. Run the REPL. Try writing some pipelines. Let me know what you think.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token function" style="color:rgb(80, 250, 123)">git</span><span class="token plain"> clone https://github.com/mcclowes/lea.git</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">cd</span><span class="token plain"> lea</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">npm</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">install</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">npm</span><span class="token plain"> run repl</span><br></span></code></pre></div></div>]]></content>
        <author>
            <name>Max Clayton Clowes</name>
            <uri>https://github.com/mcclowes</uri>
        </author>
        <category label="projects" term="projects"/>
        <category label="dev" term="dev"/>
        <category label="programming languages" term="programming languages"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Prefix]]></title>
        <id>https://mcclowes.com/blog/2025/11/18/prefix</id>
        <link href="https://mcclowes.com/blog/2025/11/18/prefix"/>
        <updated>2025-11-18T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Prefix game screenshot]]></summary>
        <content type="html"><![CDATA[<p><img decoding="async" loading="lazy" alt="Prefix game screenshot" src="https://mcclowes.com/assets/images/prefix-c1367524adc13ffa95dbbfff2fdb8fe3.png" width="2924" height="1766" class="img_ev3q"></p>
<p>I've been working on a new game called Prefix.</p>
<p>You have to guess the word of the day (usually a 5-12 letter word). You start with the first letter revealed. By guessing words that fit given the letters revealed so far, you gradually arrive at the right answer.</p>
<p>You can play it <a href="https://prefix.mcclowes.com/" target="_blank" rel="noopener noreferrer" class="">here</a>.</p>
<div class="wrapper_pkwN fadeRight_Lfa0" role="region" aria-label="Image carousel" aria-roledescription="carousel" tabindex="0"><div class="carouselTrack_mGOp" role="group" aria-label="Carousel images"><p class="imageContainer_Slcn markdown"><img src="https://mcclowes.com/img/posts/puzzles/prefix-o-1.PNG" alt="Image 1 of 3" class="image_i3UB markdown-img"></p><p class="imageContainer_Slcn markdown"><img src="https://mcclowes.com/img/posts/puzzles/prefix-o-2.PNG" alt="Image 2 of 3" class="image_i3UB markdown-img"></p><p class="imageContainer_Slcn markdown"><img src="https://mcclowes.com/img/posts/puzzles/prefix-o-3.PNG" alt="Image 3 of 3" class="image_i3UB markdown-img"></p></div></div>
<p>It looks a bit like Wordle, but it's actually an attempt to make a 1-player version of the fantastic word game <a href="https://www.ludozofi.com/home/games/contact/" target="_blank" rel="noopener noreferrer" class="">Contact</a>.</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="how-to-play">How to play<a href="https://mcclowes.com/blog/2025/11/18/prefix#how-to-play" class="hash-link" aria-label="Direct link to How to play" title="Direct link to How to play" translate="no">​</a></h3>
<div style="position:relative;padding-bottom:56.25%;height:0"><iframe src="https://www.loom.com/embed/748a8362041e4ba1b8d57315926be0f4" title="Embedded Loom Video" frameborder="0" allowfullscreen="" loading="lazy" style="position:absolute;top:0;left:0;width:100%;height:100%"></iframe></div>]]></content>
        <author>
            <name>Max Clayton Clowes</name>
            <uri>https://github.com/mcclowes</uri>
        </author>
        <category label="game design" term="game design"/>
        <category label="dev" term="dev"/>
        <category label="gaming" term="gaming"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Paintings November 25]]></title>
        <id>https://mcclowes.com/blog/2025/11/12/paintings-november-25</id>
        <link href="https://mcclowes.com/blog/2025/11/12/paintings-november-25"/>
        <updated>2025-11-12T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[<Carousel]]></summary>
        <content type="html"><![CDATA[<div class="wrapper_pkwN fadeRight_Lfa0" role="region" aria-label="Image carousel" aria-roledescription="carousel" tabindex="0"><div class="carouselTrack_mGOp" role="group" aria-label="Carousel images"><p class="imageContainer_Slcn markdown"><img src="https://mcclowes.com/img/posts/paintings-nov-25/isa-4.jpeg" alt="Image 1 of 4" class="image_i3UB markdown-img"></p><p class="imageContainer_Slcn markdown"><img src="https://mcclowes.com/img/posts/paintings-nov-25/isa-3.jpeg" alt="Image 2 of 4" class="image_i3UB markdown-img"></p><p class="imageContainer_Slcn markdown"><img src="https://mcclowes.com/img/posts/paintings-nov-25/isa-2.jpeg" alt="Image 3 of 4" class="image_i3UB markdown-img"></p><p class="imageContainer_Slcn markdown"><img src="https://mcclowes.com/img/posts/paintings-nov-25/isa-1.jpeg" alt="Image 4 of 4" class="image_i3UB markdown-img"></p></div></div>
<p>WIP</p>
<div class="wrapper_pkwN fadeRight_Lfa0" role="region" aria-label="Image carousel" aria-roledescription="carousel" tabindex="0"><div class="carouselTrack_mGOp" role="group" aria-label="Carousel images"><p class="imageContainer_Slcn markdown"><img src="https://mcclowes.com/img/posts/paintings-nov-25/cat.jpeg" alt="Image 1 of 3" class="image_i3UB markdown-img"></p><p class="imageContainer_Slcn markdown"><img src="https://mcclowes.com/img/posts/paintings-nov-25/flowers.jpeg" alt="Image 2 of 3" class="image_i3UB markdown-img"></p><p class="imageContainer_Slcn markdown"><img src="https://mcclowes.com/img/posts/paintings-nov-25/isa-leaves.jpeg" alt="Image 3 of 3" class="image_i3UB markdown-img"></p></div></div>]]></content>
        <author>
            <name>Max Clayton Clowes</name>
            <uri>https://github.com/mcclowes</uri>
        </author>
        <category label="art" term="art"/>
        <category label="painting" term="painting"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[PWAs could still be good]]></title>
        <id>https://mcclowes.com/blog/2025/11/10/pwas-are-almost-good</id>
        <link href="https://mcclowes.com/blog/2025/11/10/pwas-are-almost-good"/>
        <updated>2025-11-10T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[I just upgraded my Fantasy Premier League helper into a Progressive Web App (PWA).]]></summary>
        <content type="html"><![CDATA[<p>I just upgraded my <a href="https://mcclowes.com/blog/2025/11/04/what-the-fpl" target="_blank" rel="noopener noreferrer" class="">Fantasy Premier League helper</a> into a Progressive Web App (<span class="glossaryTermWrapper_tlxd"><a href="https://mcclowes.com/glossary#pwa" class="glossaryTerm_WE8X" aria-describedby="tooltip-pwa">PWA</a><span id="tooltip-pwa" class="tooltip_MOZH tooltipTop_HWYP tooltipFloating_XSLb" role="tooltip"><strong>PWA</strong> <!-- -->A Progressive Web App (PWA) is a type of web application that is built with progressive enhancement, rather than traditional web development techniques. PWAs are designed to be fast, reliable, and installable on a user's device.</span></span>).</p>
<p>I haven't really touched the tech since 2017, and my main memory of them ws encountering issues with them caching code and breaking sites (which is still a risk but was also probably mainly inexperience). Looking into them 8 years later, I was surprised to see...</p>
<p>###&nbsp;1. It's so fully featured</p>
<p>And the features are pretty easy to set up.</p>
<p>You can see everything a <span class="glossaryTermWrapper_tlxd"><a href="https://mcclowes.com/glossary#pwa" class="glossaryTerm_WE8X" aria-describedby="tooltip-pwa">PWA</a><span id="tooltip-pwa" class="tooltip_MOZH tooltipTop_HWYP tooltipFloating_XSLb" role="tooltip"><strong>PWA</strong> <!-- -->A Progressive Web App (PWA) is a type of web application that is built with progressive enhancement, rather than traditional web development techniques. PWAs are designed to be fast, reliable, and installable on a user's device.</span></span> can do here: <a href="https://whatpwacando.today/" target="_blank" rel="noopener noreferrer" class="">https://whatpwacando.today/</a></p>
<p>I've found implementing notifications the <span class="glossaryTermWrapper_tlxd"><a href="https://mcclowes.com/glossary#pwa" class="glossaryTerm_WE8X" aria-describedby="tooltip-pwa">PWA</a><span id="tooltip-pwa" class="tooltip_MOZH tooltipTop_HWYP tooltipFloating_XSLb" role="tooltip"><strong>PWA</strong> <!-- -->A Progressive Web App (PWA) is a type of web application that is built with progressive enhancement, rather than traditional web development techniques. PWAs are designed to be fast, reliable, and installable on a user's device.</span></span> way pretty nice and the storage features are great. For instance, having offline-access to my full FPL database is great, when the official FPL doesn't work offline.</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="2-iphone-supports-so-little-of-it">2. iPhone supports so little of it<a href="https://mcclowes.com/blog/2025/11/10/pwas-are-almost-good#2-iphone-supports-so-little-of-it" class="hash-link" aria-label="Direct link to 2. iPhone supports so little of it" title="Direct link to 2. iPhone supports so little of it" translate="no">​</a></h3>
<p>If you go through each of those features on your mobile device, you'll probably see that most of them, especialy the cool most native feeling things, aren't supported.</p>
<p>Given <a href="https://www.youtube.com/watch?v=oUsXoz-wbs4" target="_blank" rel="noopener noreferrer" class="">Steve Jobs was such a PWA-believer</a>, it's disappointing that Apple is so behind the state-of-the-art in <span class="glossaryTermWrapper_tlxd"><a href="https://mcclowes.com/glossary#pwa" class="glossaryTerm_WE8X" aria-describedby="tooltip-pwa">PWA</a><span id="tooltip-pwa" class="tooltip_MOZH tooltipTop_HWYP tooltipFloating_XSLb" role="tooltip"><strong>PWA</strong> <!-- -->A Progressive Web App (PWA) is a type of web application that is built with progressive enhancement, rather than traditional web development techniques. PWAs are designed to be fast, reliable, and installable on a user's device.</span></span> support (though obviously Apple has plenty of reasons to fear exposing near-native app experiences without the need to go through app certification).</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="3-its-easy-to-set-up-in-your-project">3. It's easy to set up in your project<a href="https://mcclowes.com/blog/2025/11/10/pwas-are-almost-good#3-its-easy-to-set-up-in-your-project" class="hash-link" aria-label="Direct link to 3. It's easy to set up in your project" title="Direct link to 3. It's easy to set up in your project" translate="no">​</a></h3>
<p>If you've already done the hard part of implementing an app, the pretty boilerplated work of retrofitting your web app into a <span class="glossaryTermWrapper_tlxd"><a href="https://mcclowes.com/glossary#pwa" class="glossaryTerm_WE8X" aria-describedby="tooltip-pwa">PWA</a><span id="tooltip-pwa" class="tooltip_MOZH tooltipTop_HWYP tooltipFloating_XSLb" role="tooltip"><strong>PWA</strong> <!-- -->A Progressive Web App (PWA) is a type of web application that is built with progressive enhancement, rather than traditional web development techniques. PWAs are designed to be fast, reliable, and installable on a user's device.</span></span> is trivial for Claude/Cursor.</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="but-no-one-knows-how-to-install-them">...but no one knows (how) to install them<a href="https://mcclowes.com/blog/2025/11/10/pwas-are-almost-good#but-no-one-knows-how-to-install-them" class="hash-link" aria-label="Direct link to ...but no one knows (how) to install them" title="Direct link to ...but no one knows (how) to install them" translate="no">​</a></h3>
<p>Ultimately though, very few people ever 'Add to home screen' on iPhone (or know it's an option), and even less see the 'Install app' icon in the Chrome URL bar. It's so hidden away in these UIs.</p>
<p>It was a nice reminder to me to see what apps I use regularly already have <span class="glossaryTermWrapper_tlxd"><a href="https://mcclowes.com/glossary#pwa" class="glossaryTerm_WE8X" aria-describedby="tooltip-pwa">PWA</a><span id="tooltip-pwa" class="tooltip_MOZH tooltipTop_HWYP tooltipFloating_XSLb" role="tooltip"><strong>PWA</strong> <!-- -->A Progressive Web App (PWA) is a type of web application that is built with progressive enhancement, rather than traditional web development techniques. PWAs are designed to be fast, reliable, and installable on a user's device.</span></span> functionality - <span class="glossaryTermWrapper_tlxd"><a href="https://mcclowes.com/glossary#vercel" class="glossaryTerm_WE8X" aria-describedby="tooltip-vercel">Vercel</a><span id="tooltip-vercel" class="tooltip_MOZH tooltipTop_HWYP tooltipFloating_XSLb" role="tooltip"><strong>vercel</strong> <!-- -->Vercel is a cloud platform for frontend developers. It is used to deploy and host websites and web applications.</span></span>, youTube, and more. It can be really nice to have a dedicated window, dock presence, etc. for your go-to web sites, and remove the clutter of the browser.</p>]]></content>
        <author>
            <name>Max Clayton Clowes</name>
            <uri>https://github.com/mcclowes</uri>
        </author>
        <category label="dev" term="dev"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[What the FPL!?]]></title>
        <id>https://mcclowes.com/blog/2025/11/04/what-the-fpl</id>
        <link href="https://mcclowes.com/blog/2025/11/04/what-the-fpl"/>
        <updated>2025-11-04T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[I've been working on a revamped version of my Fantasy Premier League helper tool, What the FPL!? I initially built the first version back in 2019, and this version is a complete rebuild with a much more solid technical foundation. The original version used a simpler predicted points algorithm, but this rebuild takes a more sophisticated approach.]]></summary>
        <content type="html"><![CDATA[<p>I've been working on a revamped version of my <a href="https://fantasy.premierleague.com/" target="_blank" rel="noopener noreferrer" class="">Fantasy Premier League</a> helper tool, <strong>What the FPL!?</strong> I initially built the <a class="" href="https://mcclowes.com/blog/2019/01/15/what-the-fpl">first version back in 2019</a>, and this version is a complete rebuild with a much more solid technical foundation. The original version used a simpler predicted points algorithm, but this rebuild takes a more sophisticated approach.</p>
<p>The project is a serverless data pipeline that mirrors the Fantasy Premier League public <span class="glossaryTermWrapper_tlxd"><a href="https://mcclowes.com/glossary#api" class="glossaryTerm_WE8X" aria-describedby="tooltip-api">API</a><span id="tooltip-api" class="tooltip_MOZH tooltipTop_HWYP tooltipFloating_XSLb" role="tooltip"><strong>API</strong> <!-- -->A set of rules and protocols that allows different software applications to communicate with each other. APIs define the methods and data formats that applications can use to request and exchange information.</span></span> into a Supabase Postgres database. It's built with Next.js App Router and runs on <span class="glossaryTermWrapper_tlxd"><a href="https://mcclowes.com/glossary#vercel" class="glossaryTerm_WE8X" aria-describedby="tooltip-vercel">Vercel</a><span id="tooltip-vercel" class="tooltip_MOZH tooltipTop_HWYP tooltipFloating_XSLb" role="tooltip"><strong>vercel</strong> <!-- -->Vercel is a cloud platform for frontend developers. It is used to deploy and host websites and web applications.</span></span>, where scheduled GitHub Actions workflows invoke the collection routines through <span class="glossaryTermWrapper_tlxd"><a href="https://mcclowes.com/glossary#api" class="glossaryTerm_WE8X" aria-describedby="tooltip-api">API</a><span id="tooltip-api" class="tooltip_MOZH tooltipTop_HWYP tooltipFloating_XSLb" role="tooltip"><strong>API</strong> <!-- -->A set of rules and protocols that allows different software applications to communicate with each other. APIs define the methods and data formats that applications can use to request and exchange information.</span></span> routes. The collected data is stored as immutable snapshots, so I can analyze historical trends and make better decisions about my team.</p>
<p><img decoding="async" loading="lazy" alt="What the FPL!? dashboard showing player data" src="https://mcclowes.com/assets/images/wtf1-a9a31639142f41cbc49487bf544f6c15.png" width="3326" height="1892" class="img_ev3q"></p>
<p>The architecture is straightforward: GitHub Actions hits a cron endpoint every hour, which triggers the data collector. The collector fetches fresh data from the FPL <span class="glossaryTermWrapper_tlxd"><a href="https://mcclowes.com/glossary#api" class="glossaryTerm_WE8X" aria-describedby="tooltip-api">API</a><span id="tooltip-api" class="tooltip_MOZH tooltipTop_HWYP tooltipFloating_XSLb" role="tooltip"><strong>API</strong> <!-- -->A set of rules and protocols that allows different software applications to communicate with each other. APIs define the methods and data formats that applications can use to request and exchange information.</span></span>, writes snapshots, and updates master tables. Everything is tracked in Supabase with job history, so I can see what's happening and when.</p>
<p><img decoding="async" loading="lazy" alt="Player statistics and gameweek information" src="https://mcclowes.com/assets/images/wtf2-7f48001af6f70c88e63a216292c9ee9d.png" width="2060" height="1744" class="img_ev3q"></p>
<p>The dashboard gives me a clean view of players, teams, and gameweek data. I can filter by position, team, status, and see historical trends through the snapshot system. But the real power of the tool comes from its team optimization features.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="team-optimization-with-custom-metrics">Team optimization with custom metrics<a href="https://mcclowes.com/blog/2025/11/04/what-the-fpl#team-optimization-with-custom-metrics" class="hash-link" aria-label="Direct link to Team optimization with custom metrics" title="Direct link to Team optimization with custom metrics" translate="no">​</a></h2>
<p>What the FPL!? helps you optimize your team using my own custom metrics, including <strong>expectation-weighted fitness</strong>. This metric goes beyond simple point totals by weighting player performance against their expected output based on their position and upcoming fixture difficulty. It gives you a clearer picture of which players are truly outperforming their cost and situation.</p>
<p>The optimization engine uses a genetic algorithm to find a (probably) optimal team composition within your budget constraints. It starts with a population of random team configurations, then evolves them over multiple generations by:</p>
<ol>
<li class=""><strong>Evaluating fitness</strong>: Each team is scored using the custom metrics, including expectation-weighted fitness</li>
<li class=""><strong>Selecting parents</strong>: The best-performing teams are selected to breed the next generation</li>
<li class=""><strong>Crossover</strong>: Combining successful team configurations to create new combinations</li>
<li class=""><strong>Mutation</strong>: Introducing random changes to explore new possibilities</li>
<li class=""><strong>Iteration</strong>: Repeating this process until the algorithm converges on optimal solutions</li>
</ol>
<p>This approach lets you explore thousands of team combinations automatically, finding lineups you might never have considered manually. You can specify constraints like budget, required players, and formation preferences, and the algorithm will find the best team that meets those requirements.</p>
<p><img decoding="async" loading="lazy" alt="Historical data and player snapshots" src="https://mcclowes.com/assets/images/wtf3-716e5cac0ac34d267fe6a5c45d978eda.png" width="2030" height="1892" class="img_ev3q"></p>
<p>The whole thing is open source and available on GitHub, with a full <span class="glossaryTermWrapper_tlxd"><a href="https://mcclowes.com/glossary#rest" class="glossaryTerm_WE8X" aria-describedby="tooltip-rest">REST</a><span id="tooltip-rest" class="tooltip_MOZH tooltipTop_HWYP tooltipFloating_XSLb" role="tooltip"><strong>REST</strong> <!-- -->An architectural style for designing networked applications. REST uses HTTP methods (GET, POST, PUT, DELETE) to perform operations on resources identified by URLs.</span></span> <span class="glossaryTermWrapper_tlxd"><a href="https://mcclowes.com/glossary#api" class="glossaryTerm_WE8X" aria-describedby="tooltip-api">API</a><span id="tooltip-api" class="tooltip_MOZH tooltipTop_HWYP tooltipFloating_XSLb" role="tooltip"><strong>API</strong> <!-- -->A set of rules and protocols that allows different software applications to communicate with each other. APIs define the methods and data formats that applications can use to request and exchange information.</span></span> for querying current and historical data. It's been a fun project to rebuild with modern tooling, and it's already helping me make better picks this season.</p>
<p>If you're curious about the original 2019 version, you can read about it <a class="" href="https://mcclowes.com/blog/2019/01/15/what-the-fpl">here</a>.</p>]]></content>
        <author>
            <name>Max Clayton Clowes</name>
            <uri>https://github.com/mcclowes</uri>
        </author>
        <category label="dev" term="dev"/>
        <category label="fantasy football" term="fantasy football"/>
        <category label="fpl" term="fpl"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[A glossary plugin for Docusaurus]]></title>
        <id>https://mcclowes.com/blog/2025/10/03/docusaurus-plugin-glossary</id>
        <link href="https://mcclowes.com/blog/2025/10/03/docusaurus-plugin-glossary"/>
        <updated>2025-10-03T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[I've always been frustrated by how technical documentation often assumes readers know all the jargon. Whenever I build a site with Docusaurus, I wanted a way to automatically link and explain key terms, so readers can hover to see definitions without breaking their flow.]]></summary>
        <content type="html"><![CDATA[<a class="card_IGiA" href="https://github.com/mcclowes/docusaurus-plugin-glossary" target="_blank" rel="noopener noreferrer"><div class="header_c7pi"><div class="repoName_SPia"><span class="githubIcon_jJyO" aria-hidden="true"></span>mcclowes/docusaurus-plugin-glossary</div><span class="badge_pz_n">Loading…</span></div><p class="description_WGYz">Fetching repository details…</p><div class="metaRow_dMDu"><span class="metaItem_kXQw" title="Stars">⭐ <!-- -->0</span><span class="metaItem_kXQw" title="Forks">🍴 <!-- -->0</span></div><svg class="githubLogo_duNS" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><path fill-rule="evenodd" clip-rule="evenodd" d="M12 2C6.477 2 2 6.477 2 12c0 4.42 2.865 8.17 6.839 9.49.5.092.682-.217.682-.482 0-.237-.008-.866-.013-1.7-2.782.603-3.369-1.34-3.369-1.34-.454-1.156-1.11-1.463-1.11-1.463-.908-.62.069-.608.069-.608 1.003.07 1.531 1.03 1.531 1.03.892 1.529 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.11-4.555-4.943 0-1.091.39-1.984 1.029-2.683-.103-.253-.446-1.27.098-2.647 0 0 .84-.269 2.75 1.025A9.578 9.578 0 0112 6.836c.85.004 1.705.114 2.504.336 1.909-1.294 2.747-1.025 2.747-1.025.546 1.377.203 2.394.1 2.647.64.699 1.028 1.592 1.028 2.683 0 3.842-2.339 4.687-4.566 4.935.359.309.678.919.678 1.852 0 1.336-.012 2.415-.012 2.743 0 .267.18.578.688.48C19.138 20.167 22 16.418 22 12c0-5.523-4.477-10-10-10z" fill="currentColor"></path></svg></a>
<p>I've always been frustrated by how technical documentation often assumes readers know all the <span class="glossaryTermWrapper_tlxd"><a href="https://mcclowes.com/glossary#jargon" class="glossaryTerm_WE8X" aria-describedby="tooltip-jargon">jargon</a><span id="tooltip-jargon" class="tooltip_MOZH tooltipTop_HWYP tooltipFloating_XSLb" role="tooltip"><strong>jargon</strong> <!-- -->Technical jargon is a type of language that is used to describe technical concepts in a precise and concise manner. It is often used in scientific, engineering, and technical fields.</span></span>. Whenever I build a site with <span class="glossaryTermWrapper_tlxd"><a href="https://mcclowes.com/glossary#docusaurus" class="glossaryTerm_WE8X" aria-describedby="tooltip-docusaurus">Docusaurus</a><span id="tooltip-docusaurus" class="tooltip_MOZH tooltipTop_HWYP tooltipFloating_XSLb" role="tooltip"><strong>Docusaurus</strong> <!-- -->A static site generator for React-based websites. Docusaurus is used to build this website.</span></span>, I wanted a way to automatically link and explain key terms, so readers can hover to see definitions without breaking their flow.</p>
<p>There used to be <a href="https://github.com/grnet/docusaurus-terminology" target="_blank" rel="noopener noreferrer" class="">plugins for this</a>, but these open source projects all went stale.</p>
<p>That's why I built <a href="https://github.com/mcclowes/docusaurus-plugin-glossary" target="_blank" rel="noopener noreferrer" class="">docusaurus-plugin-glossary</a> - a plugin that automatically detects glossary terms in your markdown content and turns them into interactive tooltips.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="the-solution">The solution<a href="https://mcclowes.com/blog/2025/10/03/docusaurus-plugin-glossary#the-solution" class="hash-link" aria-label="Direct link to The solution" title="Direct link to The solution" translate="no">​</a></h2>
<p>The plugin does two things well:</p>
<ol>
<li class="">
<p><strong>Interactive tooltips</strong>: Hover over any linked term (like this - <span class="glossaryTermWrapper_tlxd"><a href="https://mcclowes.com/glossary#docusaurus" class="glossaryTerm_WE8X" aria-describedby="tooltip-docusaurus">Docusaurus</a><span id="tooltip-docusaurus" class="tooltip_MOZH tooltipTop_HWYP tooltipFloating_XSLb" role="tooltip"><strong>Docusaurus</strong> <!-- -->A static site generator for React-based websites. Docusaurus is used to build this website.</span></span>) and see its definition instantly. Click to navigate to the full glossary page for related terms.</p>
</li>
<li class="">
<p><strong>Automatic term detection</strong>: It scans your markdown files and automatically links glossary terms with a subtle dotted underline. No manual markup required.</p>
</li>
</ol>
<p>The magic happens through a remark plugin that processes your markdown before it hits the browser. It respects word boundaries (so "<span class="glossaryTermWrapper_tlxd"><a href="https://mcclowes.com/glossary#api" class="glossaryTerm_WE8X" aria-describedby="tooltip-api">API</a><span id="tooltip-api" class="tooltip_MOZH tooltipTop_HWYP tooltipFloating_XSLb" role="tooltip"><strong>API</strong> <!-- -->A set of rules and protocols that allows different software applications to communicate with each other. APIs define the methods and data formats that applications can use to request and exchange information.</span></span>" doesn't match "application"), and it smartly skips terms inside code blocks, links, or existing components.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="technical-highlights">Technical highlights<a href="https://mcclowes.com/blog/2025/10/03/docusaurus-plugin-glossary#technical-highlights" class="hash-link" aria-label="Direct link to Technical highlights" title="Direct link to Technical highlights" translate="no">​</a></h2>
<ul>
<li class="">Built as a proper <span class="glossaryTermWrapper_tlxd"><a href="https://mcclowes.com/glossary#docusaurus" class="glossaryTerm_WE8X" aria-describedby="tooltip-docusaurus">Docusaurus</a><span id="tooltip-docusaurus" class="tooltip_MOZH tooltipTop_HWYP tooltipFloating_XSLb" role="tooltip"><strong>Docusaurus</strong> <!-- -->A static site generator for React-based websites. Docusaurus is used to build this website.</span></span> plugin following their plugin architecture</li>
<li class="">Uses remark to process markdown at build time</li>
<li class="">React components for the tooltip UI and glossary page</li>
<li class="">Full TypeScript support</li>
<li class="">Configurable term detection with sensible defaults</li>
</ul>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="try-it-out">Try it out<a href="https://mcclowes.com/blog/2025/10/03/docusaurus-plugin-glossary#try-it-out" class="hash-link" aria-label="Direct link to Try it out" title="Direct link to Try it out" translate="no">​</a></h2>
<p>You can install it from npm:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token function" style="color:rgb(80, 250, 123)">npm</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">install</span><span class="token plain"> docusaurus-plugin-glossary</span><br></span></code></pre></div></div>
<a class="card_qYam" href="https://www.npmjs.com/package/docusaurus-plugin-glossary" target="_blank" rel="noopener noreferrer"><div class="header_DOP2"><div class="packageName_Eatc"><span class="npmIcon_pFcu" aria-hidden="true">📦</span>docusaurus-plugin-glossary</div><span class="badge_FpIn">Loading…</span></div><p class="description_Xxle">Fetching package details…</p><div class="metaRow_C9rb"></div><svg class="npmLogo_xLEc" viewBox="0 0 27.23 27.23" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><rect width="27.23" height="27.23" rx="2" fill="#CB3837"></rect><polygon fill="#fff" points="5.8 21.75 13.66 21.75 13.67 9.98 17.59 9.98 17.58 21.76 21.51 21.76 21.52 6.06 5.82 6.04 5.8 21.75"></polygon></svg></a>
<p>Or check out the <a href="https://github.com/mcclowes/docusaurus-plugin-glossary" target="_blank" rel="noopener noreferrer" class="">GitHub repository</a> for documentation, examples, and source code.</p>
<hr>
<p><strong>Links:</strong></p>
<ul>
<li class=""><a href="https://github.com/mcclowes/docusaurus-plugin-glossary" target="_blank" rel="noopener noreferrer" class="">GitHub Repository</a></li>
<li class=""><a href="https://www.npmjs.com/package/docusaurus-plugin-glossary" target="_blank" rel="noopener noreferrer" class="">npm Package</a></li>
<li class=""><a href="https://github.com/mcclowes/docusaurus-plugin-glossary#readme" target="_blank" rel="noopener noreferrer" class="">Documentation</a></li>
<li class=""><a href="https://github.com/mcclowes/claude-docusaurus-skills" target="_blank" rel="noopener noreferrer" class="">Claude Docusaurus Skills</a></li>
</ul>]]></content>
        <author>
            <name>Max Clayton Clowes</name>
            <uri>https://github.com/mcclowes</uri>
        </author>
        <category label="dev" term="dev"/>
        <category label="open source" term="open source"/>
        <category label="documentation" term="documentation"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Augury]]></title>
        <id>https://mcclowes.com/blog/2025/09/12/augury</id>
        <link href="https://mcclowes.com/blog/2025/09/12/augury"/>
        <updated>2025-09-12T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[A poem]]></summary>
        <content type="html"><![CDATA[<p><img decoding="async" loading="lazy" alt="A poem" src="https://mcclowes.com/assets/images/augury-53d00ca0988c2f220480a01ce2b1fa2d.jpeg" width="4284" height="3039" class="img_ev3q"></p>
<p>How cool is <a href="https://goodpress.co.uk/pages/the-paper" target="_blank" rel="noopener noreferrer" class="">Good Press</a>?!</p>]]></content>
        <author>
            <name>Max Clayton Clowes</name>
            <uri>https://github.com/mcclowes</uri>
        </author>
        <category label="writing" term="writing"/>
        <category label="poetry" term="poetry"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Election prediction and poll aggregation]]></title>
        <id>https://mcclowes.com/blog/2025/09/12/elections</id>
        <link href="https://mcclowes.com/blog/2025/09/12/elections"/>
        <updated>2025-09-12T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[I've been working on an MRP-based election modal and poll aggregation web app.]]></summary>
        <content type="html"><![CDATA[<p><img decoding="async" loading="lazy" src="https://mcclowes.com/assets/images/bo3-f3764262662efbe58a2fc3c69afc808c.png" width="3098" height="1442" class="img_ev3q"></p>
<p>I've been working on an MRP-based election modal and poll aggregation web app.</p>
<p><img decoding="async" loading="lazy" src="https://mcclowes.com/assets/images/bo1-2ea6f7695d01c7a3ad16b0db16fa8d8c.png" width="3140" height="1888" class="img_ev3q"></p>
<p><img decoding="async" loading="lazy" src="https://mcclowes.com/assets/images/bo2-8728cdcbfcbbda7fe765b8befc62d778.png" width="3028" height="1886" class="img_ev3q"></p>
<p><img decoding="async" loading="lazy" src="https://mcclowes.com/assets/images/bo4-1057e9e0c61ad8a24a57b29d9ac9eaf3.png" width="2964" height="1370" class="img_ev3q"></p>
<p>Let me know if you want access!</p>]]></content>
        <author>
            <name>Max Clayton Clowes</name>
            <uri>https://github.com/mcclowes</uri>
        </author>
        <category label="dev" term="dev"/>
        <category label="politics" term="politics"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Ode to Heather Christle]]></title>
        <id>https://mcclowes.com/blog/2025/09/01/heather-christle</id>
        <link href="https://mcclowes.com/blog/2025/09/01/heather-christle"/>
        <updated>2025-09-01T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[I own like six nail clippers]]></summary>
        <content type="html"><![CDATA[<p>I own like six nail clippers<br>
<!-- -->and I honestly can’t<br>
<!-- -->find<br>
<!-- -->even one</p>
<p>Loved <a href="https://www.littlebrown.co.uk/titles/heather-christle/paper-crown/9781472158673/" target="_blank" rel="noopener noreferrer" class="">Paper Crown</a>. Great, fun, relatable poetry.</p>]]></content>
        <author>
            <name>Max Clayton Clowes</name>
            <uri>https://github.com/mcclowes</uri>
        </author>
        <category label="writing" term="writing"/>
        <category label="poetry" term="poetry"/>
    </entry>
</feed>