<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Craft on Alfero Chingono</title><link>https://www.chingono.com/categories/craft/</link><description>Recent content in Craft on Alfero Chingono</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><lastBuildDate>Fri, 17 Apr 2026 07:57:23 -0400</lastBuildDate><atom:link href="https://www.chingono.com/categories/craft/index.xml" rel="self" type="application/rss+xml"/><item><title>Fixing Deep Nesting in TypeScript: A Real Refactor From CueMarshal</title><link>https://www.chingono.com/blog/2026/02/10/fixing-deep-nesting-in-typescript-a-real-refactor-from-cuemarshal/</link><pubDate>Tue, 10 Feb 2026 09:00:00 +0000</pubDate><guid>https://www.chingono.com/blog/2026/02/10/fixing-deep-nesting-in-typescript-a-real-refactor-from-cuemarshal/</guid><description>&lt;img src="https://www.chingono.com/blog/2026/02/10/fixing-deep-nesting-in-typescript-a-real-refactor-from-cuemarshal/cover.png" alt="Featured image of post Fixing Deep Nesting in TypeScript: A Real Refactor From CueMarshal" /&gt;&lt;p&gt;There&amp;rsquo;s a point in the lifecycle of any project where the &amp;ldquo;quick fix&amp;rdquo; logic starts to pile up. For the &lt;a class="link" href="https://www.chingono.com/blog/2025/11/27/mcp-at-scale-how-i-used-model-context-protocol-to-connect-ai-agents-to-gitea/" &gt;Gitea MCP server&lt;/a&gt; in &lt;a class="link" href="https://www.cuemarshal.com" target="_blank" rel="noopener"
&gt;CueMarshal&lt;/a&gt;, that point was the &lt;code&gt;get_pr_diff&lt;/code&gt; tool.&lt;/p&gt;
&lt;p&gt;What started as a simple API call had grown into a 150-line function with four levels of nested &lt;code&gt;if/else&lt;/code&gt; statements. It was classic &amp;ldquo;Arrow Code&amp;rdquo;: the logic was pointed so far to the right that it was getting hard to read on a standard monitor.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s how I refactored it and the TypeScript patterns I used to make it maintainable.&lt;/p&gt;
&lt;h2 id="the-problem-the-arrow-code-trap"&gt;The Problem: The &amp;ldquo;Arrow Code&amp;rdquo; Trap
&lt;/h2&gt;&lt;p&gt;The original function had to handle multiple failure modes:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Check if the repository exists.&lt;/li&gt;
&lt;li&gt;Check if the pull request exists.&lt;/li&gt;
&lt;li&gt;Check if the diff is too large for the LLM&amp;rsquo;s context.&lt;/li&gt;
&lt;li&gt;Handle the Gitea API&amp;rsquo;s specific error formats.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If any of these failed, I had to return a structured error message. This led to a pattern of:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;repoExists&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prExists&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;diff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;getDiff&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;diff&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;LIMIT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Success!
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Error: Too large
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Error: PR missing
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Error: Repo missing
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This is hard to follow because the &amp;ldquo;Success&amp;rdquo; case is buried in the middle, and the &amp;ldquo;Error&amp;rdquo; cases are disconnected from their triggers.&lt;/p&gt;
&lt;h2 id="the-solution-guard-clauses-and-return-early"&gt;The Solution: Guard Clauses and &amp;ldquo;Return Early&amp;rdquo;
&lt;/h2&gt;&lt;p&gt;The first step was to flip the logic. Instead of nesting for success, I used &lt;strong&gt;Guard Clauses&lt;/strong&gt; to return early on failure.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;repoExists&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Repo missing&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;prExists&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;PR missing&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;diff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;getDiff&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;diff&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;LIMIT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Diff too large&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Success! (Now at the top-level indentation)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;diff&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This immediately flattened the function and made it much more readable.&lt;/p&gt;
&lt;h2 id="leveling-up-with-typescripts-result-type"&gt;Leveling Up with TypeScript&amp;rsquo;s &lt;code&gt;Result&lt;/code&gt; Type
&lt;/h2&gt;&lt;p&gt;To make this even more reliable, I introduced a &lt;code&gt;Result&lt;/code&gt; type pattern (inspired by Rust or F#). Instead of returning &lt;code&gt;null&lt;/code&gt; or throwing an error, every step of the process returns an object that explicitly states if it succeeded or failed.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;E &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="na"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ok&lt;/span&gt;: &lt;span class="kt"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;: &lt;span class="kt"&gt;T&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ok&lt;/span&gt;: &lt;span class="kt"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;: &lt;span class="kt"&gt;E&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Using this pattern, I could chain the operations together. If any step returns &lt;code&gt;{ ok: false }&lt;/code&gt;, the whole chain stops and returns that error.&lt;/p&gt;
&lt;h2 id="the-functional-refactor"&gt;The &amp;ldquo;Functional&amp;rdquo; Refactor
&lt;/h2&gt;&lt;p&gt;The final version of the &lt;code&gt;get_pr_diff&lt;/code&gt; tool now looks like this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Validate Inputs:&lt;/strong&gt; A clean, typed function that ensures the repo and PR IDs are valid.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fetch PR Data:&lt;/strong&gt; A separate function that handles the Gitea API call and maps the error codes to human-readable messages.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Process Diff:&lt;/strong&gt; A dedicated function for the logic of truncation and formatting.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;By splitting these into small, testable functions, the main &lt;code&gt;execute&lt;/code&gt; method for the tool became a simple 10-line orchestrator.&lt;/p&gt;
&lt;h2 id="why-this-matters-for-agentic-ai"&gt;Why This Matters for Agentic AI
&lt;/h2&gt;&lt;p&gt;Refactoring for &amp;ldquo;Clean Code&amp;rdquo; isn&amp;rsquo;t just for human developers anymore. When you are building &lt;a class="link" href="https://www.chingono.com/blog/2025/08/28/designing-multi-agent-systems-lessons-from-building-an-8-agent-engineering-orchestra/" &gt;Agentic Orchestras&lt;/a&gt;, your code &lt;em&gt;is&lt;/em&gt; the environment the agent has to reason about.&lt;/p&gt;
&lt;p&gt;If your codebase is full of deep nesting and &amp;ldquo;spaghetti&amp;rdquo; logic, the AI is more likely to make mistakes when trying to refactor or extend it. Clean, flat, typed code makes your system more &amp;ldquo;agent-friendly.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;The refactor of the Gitea MCP server didn&amp;rsquo;t just make my life easier; it made &lt;a class="link" href="https://www.chingono.com/blog/2025/11/27/mcp-at-scale-how-i-used-model-context-protocol-to-connect-ai-agents-to-gitea/" &gt;CueMarshal&amp;rsquo;s agents&lt;/a&gt; faster and more reliable.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Related reading:&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://www.chingono.com/blog/2025/11/27/mcp-at-scale-how-i-used-model-context-protocol-to-connect-ai-agents-to-gitea/" &gt;MCP at Scale: Connecting AI Agents to Gitea&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.chingono.com/blog/2025/08/28/designing-multi-agent-systems-lessons-from-building-an-8-agent-engineering-orchestra/" &gt;Designing Multi-Agent Systems: Lessons from an 8-Agent Orchestra&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Sandboxed Code Execution for Kids: How Judge0 and Python sys.settrace Power FireFly</title><link>https://www.chingono.com/blog/2026/01/15/sandboxed-code-execution-for-kids-how-judge0-and-python-sys-settrace-power-firefly/</link><pubDate>Thu, 15 Jan 2026 09:00:00 +0000</pubDate><guid>https://www.chingono.com/blog/2026/01/15/sandboxed-code-execution-for-kids-how-judge0-and-python-sys-settrace-power-firefly/</guid><description>&lt;img src="https://www.chingono.com/blog/2026/01/15/sandboxed-code-execution-for-kids-how-judge0-and-python-sys-settrace-power-firefly/cover.png" alt="Featured image of post Sandboxed Code Execution for Kids: How Judge0 and Python sys.settrace Power FireFly" /&gt;&lt;p&gt;When you build a platform for kids to learn to code, like &lt;a class="link" href="https://www.chingono.com/blog/2025-05-08-teaching-kids-to-code-with-bayesian-knowledge-tracing-why-i-built-firefly/" &gt;FireFly&lt;/a&gt;, the hard problem is &lt;strong&gt;safety&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Letting a developer run arbitrary code in a container is one thing. Letting a 7-year-old, who might accidentally or intentionally write an infinite loop or a memory-hogging script, run code on your servers is another.&lt;/p&gt;
&lt;p&gt;For FireFly, I needed a solution that was:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Secure:&lt;/strong&gt; No &amp;ldquo;breakouts&amp;rdquo; to the host machine.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fast:&lt;/strong&gt; Near-instant execution so the learning flow isn&amp;rsquo;t broken.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Traceable:&lt;/strong&gt; I needed to know exactly which line was running at any moment so the AI tutor could give grounded feedback.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The solution ended up being a combination of &lt;strong&gt;Judge0&lt;/strong&gt; and &lt;strong&gt;Python&amp;rsquo;s &lt;code&gt;sys.settrace()&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id="layer-1-the-hard-sandbox-judge0"&gt;Layer 1: The Hard Sandbox (Judge0)
&lt;/h2&gt;&lt;p&gt;The first line of defense is &lt;strong&gt;Judge0&lt;/strong&gt;, an open-source online code execution system. I run Judge0 in a set of Docker containers. When a student in FireFly clicks &amp;ldquo;Run,&amp;rdquo; their code is sent to the Judge0 API, which:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Creates a temporary, isolated worker.&lt;/li&gt;
&lt;li&gt;Enforces strict CPU and memory limits.&lt;/li&gt;
&lt;li&gt;Limits the execution time to a few seconds.&lt;/li&gt;
&lt;li&gt;Returns the output (or the error).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This handles the outer safety boundary. Even if a student tries to &lt;code&gt;import os; os.system('rm -rf /')&lt;/code&gt;, Judge0 catches it or confines the damage to a disposable container.&lt;/p&gt;
&lt;h2 id="layer-2-the-soft-sandbox-python-syssettrace"&gt;Layer 2: The Soft Sandbox (Python &lt;code&gt;sys.settrace&lt;/code&gt;)
&lt;/h2&gt;&lt;p&gt;Judge0 keeps the system safe, but it does not tell me why a student got stuck. To power a &lt;a class="link" href="https://www.chingono.com/blog/2025/08/05/how-i-wired-up-an-ai-tutor-to-teach-like-a-socratic-mentor-not-a-cheater/" &gt;Socratic AI Tutor&lt;/a&gt;, the system needs to see the internal state of execution: which variables change, and which lines get hit.&lt;/p&gt;
&lt;p&gt;To do that, I wrap the student&amp;rsquo;s Python code in a tracer script that uses &lt;code&gt;sys.settrace()&lt;/code&gt;. It is a built-in Python hook that lets you run a function for each executed line.&lt;/p&gt;
&lt;h3 id="how-the-tracer-works"&gt;How the Tracer Works:
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Line-by-line tracking:&lt;/strong&gt; As the code runs, the tracer records the current line number and the values of local variables.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Instruction limit:&lt;/strong&gt; If the code takes too many steps, as in an infinite loop, the tracer raises a custom exception and stops execution before Judge0 has to step in.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;State snapshot:&lt;/strong&gt; At the end of the run, the tracer returns a breadcrumb trail of the execution.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="layer-3-the-ai-tutor-feedback-loop"&gt;Layer 3: The AI Tutor Feedback Loop
&lt;/h2&gt;&lt;p&gt;The &amp;ldquo;breadcrumb&amp;rdquo; from the tracer is what makes the &lt;a class="link" href="https://www.chingono.com/blog/2025/08/05/how-i-wired-up-an-ai-tutor-to-teach-like-a-socratic-mentor-not-a-cheater/" &gt;FireFly AI Tutor&lt;/a&gt; so effective. Instead of just seeing &amp;ldquo;Error: NameError: name &amp;lsquo;x&amp;rsquo; is not defined,&amp;rdquo; the AI can see: &amp;ldquo;The student defined &lt;code&gt;x&lt;/code&gt; on line 2, but they are trying to use it on line 5 inside a function where it&amp;rsquo;s not in scope.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;This level of detail allows the AI to ask much better &lt;a class="link" href="https://www.chingono.com/blog/2025/08/05/how-i-wired-up-an-ai-tutor-to-teach-like-a-socratic-mentor-not-a-cheater/" &gt;Socratic questions&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="why-this-matters-for-edtech"&gt;Why This Matters for EdTech
&lt;/h2&gt;&lt;p&gt;We often think of &amp;ldquo;sandboxing&amp;rdquo; as a security feature for protecting servers. In EdTech, it is also a &lt;strong&gt;pedagogical feature&lt;/strong&gt;. A safe, observable environment gives kids room to experiment, break things, and learn from their mistakes without real-world consequences.&lt;/p&gt;
&lt;p&gt;Building this part of FireFly has been one of the most satisfying engineering problems in the project. It sits right where security requirements and teaching goals meet.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Related reading:&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://www.chingono.com/blog/2025/08/05/how-i-wired-up-an-ai-tutor-to-teach-like-a-socratic-mentor-not-a-cheater/" &gt;How I Wired Up an AI Tutor to Teach Like a Socratic Mentor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.chingono.com/blog/2025-05-08-teaching-kids-to-code-with-bayesian-knowledge-tracing-why-i-built-firefly/" &gt;Teaching Kids to Code With BKT: Why I Built FireFly&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Building a Multi-Currency App: The Edge Cases Nobody Warns You About</title><link>https://www.chingono.com/blog/2025/10/22/building-a-multi-currency-app-the-edge-cases-nobody-warns-you-about/</link><pubDate>Wed, 22 Oct 2025 09:00:00 +0000</pubDate><guid>https://www.chingono.com/blog/2025/10/22/building-a-multi-currency-app-the-edge-cases-nobody-warns-you-about/</guid><description>&lt;img src="https://www.chingono.com/blog/2025/10/22/building-a-multi-currency-app-the-edge-cases-nobody-warns-you-about/cover.png" alt="Featured image of post Building a Multi-Currency App: The Edge Cases Nobody Warns You About" /&gt;&lt;p&gt;When I started building &lt;a class="link" href="https://www.famorize.com" target="_blank" rel="noopener"
&gt;Famorize&lt;/a&gt;, I thought multi-currency support would be a weekend feature. &amp;ldquo;Just store the currency code and multiply by the exchange rate, right?&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Wrong.&lt;/p&gt;
&lt;p&gt;Dealing with money in software is a masterclass in edge cases, data integrity, and precision. If you&amp;rsquo;re building an app that handles more than one currency, especially one designed for families across borders, the complexity stacks up fast.&lt;/p&gt;
&lt;p&gt;Here are the four big lessons I learned from building Famorize&amp;rsquo;s multi-currency engine.&lt;/p&gt;
&lt;h2 id="1-never-ever-use-floating-point-numbers"&gt;1. Never, Ever Use Floating Point Numbers
&lt;/h2&gt;&lt;p&gt;This is the &amp;ldquo;Day 1&amp;rdquo; rule for financial software, yet I still see it in production. Floating point numbers (like &lt;code&gt;double&lt;/code&gt; or &lt;code&gt;float&lt;/code&gt; in most languages) are inherently imprecise for decimals. $0.1 + 0.2$ in IEEE 754 floating point is not $0.3$; it&amp;rsquo;s $0.30000000000000004$.&lt;/p&gt;
&lt;p&gt;In Famorize, everything is stored as &lt;strong&gt;integers in the smallest unit&lt;/strong&gt; (e.g., cents for USD, yen for JPY, satoshis for BTC). If a transaction is $10.50, it is stored as &lt;code&gt;1050&lt;/code&gt;. This eliminates rounding errors at the database and application levels.&lt;/p&gt;
&lt;h2 id="2-exchange-rate-drift-and-historic-validity"&gt;2. Exchange Rate &amp;ldquo;Drift&amp;rdquo; and Historic Validity
&lt;/h2&gt;&lt;p&gt;Exchange rates are not static. If a user recorded a $50 CAD transaction today, it might be worth $37 USD. If they look at that same transaction next year, it should still be $50 CAD. But what was its USD value &lt;em&gt;at the time&lt;/em&gt;?&lt;/p&gt;
&lt;p&gt;To handle this, Famorize doesn&amp;rsquo;t just store the transaction. It stores:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;The Base Amount&lt;/strong&gt; (in the user&amp;rsquo;s home currency).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Original Amount&lt;/strong&gt; (in the transaction currency).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Exchange Rate Used&lt;/strong&gt; (at the time of the transaction).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This &amp;ldquo;Snapshot Pattern&amp;rdquo; ensures that reports are accurate to the moment the money actually moved, not the current market rate.&lt;/p&gt;
&lt;h2 id="3-the-yen-problem-zero-decimal-currencies"&gt;3. The &amp;ldquo;Yen Problem&amp;rdquo; (Zero-Decimal Currencies)
&lt;/h2&gt;&lt;p&gt;Most developers assume a currency always has two decimal places. This is a trap.&lt;/p&gt;
&lt;p&gt;While USD, CAD, and EUR have two (cents), currencies like JPY (Japanese Yen) have zero, and others like BHD (Bahraini Dinar) have three. If your UI or database assumes &lt;code&gt;integer / 100&lt;/code&gt; is the decimal value, you will break for users in Japan or Bahrain.&lt;/p&gt;
&lt;p&gt;Famorize uses a &lt;strong&gt;Currency Metadata&lt;/strong&gt; table that defines the &amp;ldquo;Scale&amp;rdquo; (decimal places) for every supported currency. The display layer is responsible for formatting based on this scale.&lt;/p&gt;
&lt;h2 id="4-the-ux-of-which-currency"&gt;4. The UX of &amp;ldquo;Which Currency?&amp;rdquo;
&lt;/h2&gt;&lt;p&gt;If a user in Toronto is sending money to a family member in Nairobi, which currency should the input field show?&lt;/p&gt;
&lt;p&gt;The UX pattern that worked best for me was to always let the user select the &lt;strong&gt;Transaction Currency&lt;/strong&gt; while showing a real-time &amp;ldquo;estimated conversion&amp;rdquo; into their &lt;strong&gt;Home Currency&lt;/strong&gt;. That gives them a clearer sense of how much they are spending in the units they understand.&lt;/p&gt;
&lt;h2 id="the-result"&gt;The Result
&lt;/h2&gt;&lt;p&gt;Building a multi-currency app taught me that &amp;ldquo;precision&amp;rdquo; is not just a technical requirement; it is a trust requirement. If a financial app is off by even one cent, the user&amp;rsquo;s trust is broken.&lt;/p&gt;
&lt;p&gt;Famorize now handles dozens of currencies with an integer-based engine that I can trust with real family finances. It took more than a weekend, but the lessons were worth the effort.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Related reading:&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://www.chingono.com/blog/2025/09/15/oauth2-and-oidc-for-solo-developers-a-practical-setup-with-docker/" &gt;OAuth2 and OIDC for Solo Developers: A Practical Setup With Docker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.chingono.com/blog/2025/02/15/why-i-started-building-my-own-devops-platform-and-what-i-learned/" &gt;Why I Started Building My Own DevOps Platform&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>OAuth2 and OIDC for Solo Developers: A Practical Setup With Docker</title><link>https://www.chingono.com/blog/2025/09/15/oauth2-and-oidc-for-solo-developers-a-practical-setup-with-docker/</link><pubDate>Mon, 15 Sep 2025 09:00:00 +0000</pubDate><guid>https://www.chingono.com/blog/2025/09/15/oauth2-and-oidc-for-solo-developers-a-practical-setup-with-docker/</guid><description>&lt;img src="https://www.chingono.com/blog/2025/09/15/oauth2-and-oidc-for-solo-developers-a-practical-setup-with-docker/cover.png" alt="Featured image of post OAuth2 and OIDC for Solo Developers: A Practical Setup With Docker" /&gt;&lt;p&gt;Authentication is the classic &amp;ldquo;black hole&amp;rdquo; of side projects. You start with a simple idea, and four days later you&amp;rsquo;re still reading RFC 6749 and wondering if you should just use a password in a plain text file (don&amp;rsquo;t).&lt;/p&gt;
&lt;p&gt;For my personal projects like &lt;a class="link" href="https://www.chingono.com/blog/2025/10/22/building-a-multi-currency-app-the-edge-cases-nobody-warns-you-about/" &gt;Famorize&lt;/a&gt; and &lt;a class="link" href="https://www.chingono.com/blog/2025-05-08-teaching-kids-to-code-with-bayesian-knowledge-tracing-why-i-built-firefly/" &gt;FireFly&lt;/a&gt;, I needed something standards-compliant and easy to manage across multiple services. It also had to be dependable. I didn&amp;rsquo;t want to pay $50/month for a managed identity provider (IdP) for projects that were still in development, but I also didn&amp;rsquo;t want to roll my own crypto.&lt;/p&gt;
&lt;p&gt;The solution was a self-hosted OIDC setup using Docker.&lt;/p&gt;
&lt;h2 id="why-oidc"&gt;Why OIDC?
&lt;/h2&gt;&lt;p&gt;If you&amp;rsquo;re building a &amp;ldquo;modern&amp;rdquo; app, you&amp;rsquo;re likely using a decoupled architecture: a frontend (React/Next.js/Hugo) and one or more backend APIs. You need a way to:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Log a user in.&lt;/li&gt;
&lt;li&gt;Prove to the API that the user is who they say they are.&lt;/li&gt;
&lt;li&gt;Do it securely without sharing passwords everywhere.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;OpenID Connect (OIDC) is the standard way to do this. It sits on top of OAuth2 and provides the &amp;ldquo;identity&amp;rdquo; layer.&lt;/p&gt;
&lt;h2 id="the-stack"&gt;The Stack
&lt;/h2&gt;&lt;p&gt;For my local and production environments, I settled on a &amp;ldquo;trio&amp;rdquo; of tools that play very well together in Docker Compose.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Authelia:&lt;/strong&gt; A lightweight, self-hosted IdP. It&amp;rsquo;s fast, has a great UI, and supports multi-factor authentication (MFA) out of the box.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Redis:&lt;/strong&gt; For session storage and rate-limiting.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Nginx Proxy Manager (or Traefik):&lt;/strong&gt; To handle the routing and SSL termination.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="the-local-first-workflow"&gt;The &amp;ldquo;Local First&amp;rdquo; Workflow
&lt;/h2&gt;&lt;p&gt;The biggest pain point with OIDC is setting it up locally. Most managed services require a public redirect URI, which is a nightmare for &lt;code&gt;localhost&lt;/code&gt; development.&lt;/p&gt;
&lt;p&gt;With a self-hosted setup, I can use a local domain (like &lt;code&gt;auth.local.chingono.com&lt;/code&gt;) and point it to my Docker container. This means my local development environment is &lt;em&gt;identical&lt;/em&gt; to production. If it works on my machine, the OIDC flow will work in the cloud.&lt;/p&gt;
&lt;h2 id="key-lessons-learned"&gt;Key Lessons Learned
&lt;/h2&gt;&lt;p&gt;Building this into my projects taught me a few hard-won lessons:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Scopes and Claims are everything.&lt;/strong&gt; Spend the time to understand the difference. Don&amp;rsquo;t just request &lt;code&gt;openid profile email&lt;/code&gt; and hope for the best. Define only what your app actually needs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HTTPS is non-negotiable.&lt;/strong&gt; Even for local development, OIDC requires secure cookies. Use &lt;code&gt;mkcert&lt;/code&gt; to generate local SSL certificates and save yourself the headache of &amp;ldquo;Insecure Connection&amp;rdquo; errors.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Token rotation matters.&lt;/strong&gt; If you&amp;rsquo;re building a long-lived app (like a mobile client for &lt;a class="link" href="https://www.chingono.com/blog/2025/10/22/building-a-multi-currency-app-the-edge-cases-nobody-warns-you-about/" &gt;Famorize&lt;/a&gt;), you need to handle refresh tokens correctly. Don&amp;rsquo;t just set a 30-day expiration on your access tokens.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="why-not-just-use-auth0-or-clerk"&gt;Why Not Just Use Auth0 or Clerk?
&lt;/h2&gt;&lt;p&gt;Managed services are great for teams with a budget and no time. But as a solo developer who enjoys the &amp;ldquo;Build in Public&amp;rdquo; ethos, I find that owning my identity layer is worth the extra effort. It gives me complete control over the user experience, total data sovereignty, and a much deeper understanding of the security architecture.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re interested in the specific Docker Compose configurations I use, stay tuned for the next post in this &amp;ldquo;Craft&amp;rdquo; series.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Related reading:&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://www.chingono.com/blog/2025/10/22/building-a-multi-currency-app-the-edge-cases-nobody-warns-you-about/" &gt;Building a Multi-Currency App: The Edge Cases Nobody Warns You About&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.chingono.com/blog/why-i-run-openclaw-in-docker-on-my-own-machine/" &gt;Why I Run OpenClaw in Docker on My Own Machine&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item></channel></rss>