<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[stdout]]></title><description><![CDATA[Random thoughts on software development and sometimes life in general. By Kristian Dupont.]]></description><link>https://thoughts.kristiandupont.com</link><image><url>https://substackcdn.com/image/fetch/$s_!gctQ!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0ac7c2c-b010-4699-996d-9a569881e34a_608x608.png</url><title>stdout</title><link>https://thoughts.kristiandupont.com</link></image><generator>Substack</generator><lastBuildDate>Mon, 18 May 2026 22:20:43 GMT</lastBuildDate><atom:link href="https://thoughts.kristiandupont.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Kristian Dupont]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[kristiandupont@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[kristiandupont@substack.com]]></itunes:email><itunes:name><![CDATA[Kristian Dupont]]></itunes:name></itunes:owner><itunes:author><![CDATA[Kristian Dupont]]></itunes:author><googleplay:owner><![CDATA[kristiandupont@substack.com]]></googleplay:owner><googleplay:email><![CDATA[kristiandupont@substack.com]]></googleplay:email><googleplay:author><![CDATA[Kristian Dupont]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[txtfold: surfacing signal in large inputs for LLMs]]></title><description><![CDATA[We've been living through the free-lunch era of LLM usage. Three levers are going to define what comes after.]]></description><link>https://thoughts.kristiandupont.com/p/txtfold-surfacing-signal-in-large</link><guid isPermaLink="false">https://thoughts.kristiandupont.com/p/txtfold-surfacing-signal-in-large</guid><dc:creator><![CDATA[Kristian Dupont]]></dc:creator><pubDate>Sun, 26 Apr 2026 15:24:48 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!iAAv!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0b75406-ff0a-49c0-8fe1-d9e42b38dd9f_1156x1160.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I made a tool called <a href="https://kristiandupont.github.io/txtfold/">txtfold</a>. It takes a large file, think log files, big JSON dumps, anything with a lot of repetition, and surfaces the interesting bits in a form that's suited for LLM consumption. It's written in Rust, with a CLI and Python and JS/TS bindings, and it's open source.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!iAAv!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0b75406-ff0a-49c0-8fe1-d9e42b38dd9f_1156x1160.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!iAAv!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0b75406-ff0a-49c0-8fe1-d9e42b38dd9f_1156x1160.png 424w, https://substackcdn.com/image/fetch/$s_!iAAv!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0b75406-ff0a-49c0-8fe1-d9e42b38dd9f_1156x1160.png 848w, https://substackcdn.com/image/fetch/$s_!iAAv!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0b75406-ff0a-49c0-8fe1-d9e42b38dd9f_1156x1160.png 1272w, https://substackcdn.com/image/fetch/$s_!iAAv!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0b75406-ff0a-49c0-8fe1-d9e42b38dd9f_1156x1160.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!iAAv!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0b75406-ff0a-49c0-8fe1-d9e42b38dd9f_1156x1160.png" width="1156" height="1160" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f0b75406-ff0a-49c0-8fe1-d9e42b38dd9f_1156x1160.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1160,&quot;width&quot;:1156,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1901505,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://thoughts.kristiandupont.com/i/191346796?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0b75406-ff0a-49c0-8fe1-d9e42b38dd9f_1156x1160.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!iAAv!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0b75406-ff0a-49c0-8fe1-d9e42b38dd9f_1156x1160.png 424w, https://substackcdn.com/image/fetch/$s_!iAAv!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0b75406-ff0a-49c0-8fe1-d9e42b38dd9f_1156x1160.png 848w, https://substackcdn.com/image/fetch/$s_!iAAv!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0b75406-ff0a-49c0-8fe1-d9e42b38dd9f_1156x1160.png 1272w, https://substackcdn.com/image/fetch/$s_!iAAv!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0b75406-ff0a-49c0-8fe1-d9e42b38dd9f_1156x1160.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>I think we&#8217;re facing a pivotal moment. We&#8217;ve all been enjoying frontier model access at prices that don&#8217;t reflect the underlying cost structure, because VC money was paying the difference. That&#8217;s about to change<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-1" href="#footnote-1" target="_self">1</a>. The capex is real, the providers are burning money to acquire us as users, and that can&#8217;t continue indefinitely.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://thoughts.kristiandupont.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading stdout! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>Because of that, I think we are all going to be learning how to use LLMs more effectively. I can see three levers:</p><ol><li><p><strong>Routing</strong>. We are starting to see this, but still, as of right now, model selection is more or less a config option. The big-model-for-everything pattern is wasteful: a subtask that parses a git commit message and pipes it back to an orchestrator does not need a frontier model. Smaller models aren't just cheaper either; they're faster and have lower latency variance, which compounds in agentic loops where you're chaining many calls. I expect high-granularity routing, between model sizes, between providers, and between hosted and local, to become a natural part of any coding tool. </p></li><li><p><strong>Caching</strong>. My intuition right now is that by far the most tokens are wasted by re-scanning the same parts of the code base when starting new tasks. I know that Claude Code does some caching internally, but I am thinking along the lines of &#8220;manual caching&#8221; by adding English prose in the form of comments and markdown files everywhere. I go into some details about this in <a href="https://thoughts.kristiandupont.com/p/the-robot-is-not-a-junior-developer">The Robot is not a Junior Developer. It&#8217;s a Senior Developer caught in Groundhog Day</a>. You can see how I do this in practice in the root <a href="https://github.com/kristiandupont/txtfold/blob/main/AGENTS.md#general-filefolder-structure">AGENTS.md</a> file. It&#8217;s also why I recently <a href="https://thoughts.kristiandupont.com/p/kanel-v4-generate-markdown-from-postgres">upgraded Kanel</a> to support generating markdown documentation of a PostgreSQL database, so the canonical description of the schema exists once, in a stable form. </p></li><li><p><strong>Trimming</strong>. Tools like <a href="https://www.rtk-ai.app/">rtk</a> help optimize how many tokens are spent on output from tool calls. <a href="https://dev.to/onsen/caveman-claude-the-token-cutting-skill-thats-changing-ai-workflows-4hmc">Speaking like a caveman</a> can apparently help, though I haven&#8217;t tried that myself. And this is the lever txtfold pulls: reducing large bodies of text to something a model can actually work with.</p></li></ol><p>When I started creating txtfold, I had a vision of <em>compression</em> in mind. I'd hit two situations that motivated it: a pile of large log files, and a 40MB JSON file. I knew that they would contain mostly repetitive data, with a few interesting outliers. I also knew that they were too large for the context window of any LLM, so even though I <em>could</em> just tell one to look at them, I didn&#8217;t trust what they would actually do. </p><p>That led me to thinking about something like RLE or LZW, but producing text. Such algorithms are, after all, designed exactly for retaining important information while contracting repetition. This turned out to be a dead end because the output was still very verbose, and the losslessness wasn't buying anything useful.</p><p>So I went the other way: lossy but deterministic reduction. The important bit here is that there is no AI inside txtfold trying to assess what is important and what is not. It&#8217;s all purely algorithmic. Instead, you can let your LLM play with it and configuration options until you get a useful summary out. Once you have such a config (&#8220;recipe&#8221;), you can apply it repeatedly to new text from the same source and get consistent results.</p><p>That determinism matters. An LLM-based summarizer is a black box you have to re-trust on every input. A txtfold recipe is a thing you can read, version, and reason about.</p><p>txtfold is a small bet on one of three levers. The other two need their own tools, many of which already exist. If you're building in this space, I'd love to hear about it.</p><p><a href="https://kristiandupont.github.io/txtfold/">https://kristiandupont.github.io/txtfold/</a></p><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-1" href="#footnote-anchor-1" class="footnote-number" contenteditable="false" target="_self">1</a><div class="footnote-content"><p>I should be cautious with such predictions. 20 years ago, I <a href="https://www.gamedeveloper.com/programming/the-transition-to-concurrency">asserted</a> that a concurrency wave was about to hit the industry. If you had told me that in the year 2026, I am counting not the number of giga- or even kilo-cores in my CPU, but only just above single digits, I would never have believed you! Anyhoo..</p></div></div>]]></content:encoded></item><item><title><![CDATA[Kanel v4: Generate Markdown from Postgres]]></title><description><![CDATA[Give your LLM all the details about your database without MCP's]]></description><link>https://thoughts.kristiandupont.com/p/kanel-v4-generate-markdown-from-postgres</link><guid isPermaLink="false">https://thoughts.kristiandupont.com/p/kanel-v4-generate-markdown-from-postgres</guid><dc:creator><![CDATA[Kristian Dupont]]></dc:creator><pubDate>Thu, 02 Apr 2026 08:14:50 GMT</pubDate><enclosure url="https://images.unsplash.com/photo-1468779036391-52341f60b55d?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw0fHxkb2N1bWVudHxlbnwwfHx8fDE3NzUxMTc1OTl8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I just published Kanel v4. Kanel was born as a tool for generating Typescript types from a live PostgreSQL database. V4 changes the architecture around so you can have  multiple distinct generators. </p><p>It comes with a new built-in generator for Markdown. This can be used to document your data model for your fellow team members, but the primary use case that I picture is LLM consumption. I&#8217;ve used an MCP server for my developer database which does work, but it feels slightly clumsy to me, and including a markdown with with an @-tag when pointing something out feels very natural.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://thoughts.kristiandupont.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading stdout! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>I&#8217;ve created an example for the dvd-rental database. You can see the result <a href="https://github.com/kristiandupont/kanel/tree/main/examples/markdown">here</a>.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://images.unsplash.com/photo-1468779036391-52341f60b55d?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw0fHxkb2N1bWVudHxlbnwwfHx8fDE3NzUxMTc1OTl8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://images.unsplash.com/photo-1468779036391-52341f60b55d?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw0fHxkb2N1bWVudHxlbnwwfHx8fDE3NzUxMTc1OTl8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1468779036391-52341f60b55d?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw0fHxkb2N1bWVudHxlbnwwfHx8fDE3NzUxMTc1OTl8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1468779036391-52341f60b55d?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw0fHxkb2N1bWVudHxlbnwwfHx8fDE3NzUxMTc1OTl8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1468779036391-52341f60b55d?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw0fHxkb2N1bWVudHxlbnwwfHx8fDE3NzUxMTc1OTl8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1456w" sizes="100vw"><img src="https://images.unsplash.com/photo-1468779036391-52341f60b55d?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw0fHxkb2N1bWVudHxlbnwwfHx8fDE3NzUxMTc1OTl8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" width="4928" height="2677" data-attrs="{&quot;src&quot;:&quot;https://images.unsplash.com/photo-1468779036391-52341f60b55d?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw0fHxkb2N1bWVudHxlbnwwfHx8fDE3NzUxMTc1OTl8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:2677,&quot;width&quot;:4928,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;A stack of thick folders on a white surface&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="A stack of thick folders on a white surface" title="A stack of thick folders on a white surface" srcset="https://images.unsplash.com/photo-1468779036391-52341f60b55d?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw0fHxkb2N1bWVudHxlbnwwfHx8fDE3NzUxMTc1OTl8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1468779036391-52341f60b55d?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw0fHxkb2N1bWVudHxlbnwwfHx8fDE3NzUxMTc1OTl8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1468779036391-52341f60b55d?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw0fHxkb2N1bWVudHxlbnwwfHx8fDE3NzUxMTc1OTl8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1468779036391-52341f60b55d?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw0fHxkb2N1bWVudHxlbnwwfHx8fDE3NzUxMTc1OTl8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Photo by <a href="https://unsplash.com/@beatriz_perez">Beatriz P&#233;rez Moya</a> on <a href="https://unsplash.com">Unsplash</a></figcaption></figure></div><p>Even if you are using an ORM that mirrors the database schema in some way, those  files often miss some of the finer nuances that are possible with Postgres. With Kanel, you can construct documentation that goes into just about as much detail as you could want. The engine has access to everything that <a href="https://github.com/kristiandupont/extract-pg-schema/">extract-pg-schema</a> exposes, which allows you to document indices, procedures, ranges, domains and most other Postgres-specific features.</p><p>Setting it up requires a bit of configuration work, as I don&#8217;t think I would be able to write a general-purpose template that would suit all teams. It works with Handlebars templates which will be used to generate the final result.</p><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://thoughts.kristiandupont.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading stdout! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[The Robot is not a Junior Developer. It Is a Senior Developer caught in Groundhog Day]]></title><description><![CDATA[I frequently encounter the notion that, when it comes to programming, AI is like a well-read junior developer.]]></description><link>https://thoughts.kristiandupont.com/p/the-robot-is-not-a-junior-developer</link><guid isPermaLink="false">https://thoughts.kristiandupont.com/p/the-robot-is-not-a-junior-developer</guid><dc:creator><![CDATA[Kristian Dupont]]></dc:creator><pubDate>Mon, 29 Dec 2025 08:18:07 GMT</pubDate><enclosure url="https://images.unsplash.com/photo-1527430253228-e93688616381?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwzfHx0b3klMjByb2JvdHxlbnwwfHx8fDE3NjY5MjMyMTB8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I frequently encounter the notion that, when it comes to programming, AI is like a well-read junior developer.</p><p>I don&#8217;t think that is a great analogy any more. At this point, Claude (my preferred provider at the moment) feels much more like a talented senior developer. But crucially, a senior developer who is experiencing a perpetual <em>day 1</em> on the team.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://thoughts.kristiandupont.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading stdout! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://images.unsplash.com/photo-1527430253228-e93688616381?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwzfHx0b3klMjByb2JvdHxlbnwwfHx8fDE3NjY5MjMyMTB8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://images.unsplash.com/photo-1527430253228-e93688616381?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwzfHx0b3klMjByb2JvdHxlbnwwfHx8fDE3NjY5MjMyMTB8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1527430253228-e93688616381?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwzfHx0b3klMjByb2JvdHxlbnwwfHx8fDE3NjY5MjMyMTB8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1527430253228-e93688616381?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwzfHx0b3klMjByb2JvdHxlbnwwfHx8fDE3NjY5MjMyMTB8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1527430253228-e93688616381?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwzfHx0b3klMjByb2JvdHxlbnwwfHx8fDE3NjY5MjMyMTB8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1456w" sizes="100vw"><img src="https://images.unsplash.com/photo-1527430253228-e93688616381?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwzfHx0b3klMjByb2JvdHxlbnwwfHx8fDE3NjY5MjMyMTB8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" width="6000" height="3368" data-attrs="{&quot;src&quot;:&quot;https://images.unsplash.com/photo-1527430253228-e93688616381?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwzfHx0b3klMjByb2JvdHxlbnwwfHx8fDE3NjY5MjMyMTB8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:3368,&quot;width&quot;:6000,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;blue plastic robot toy&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="blue plastic robot toy" title="blue plastic robot toy" srcset="https://images.unsplash.com/photo-1527430253228-e93688616381?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwzfHx0b3klMjByb2JvdHxlbnwwfHx8fDE3NjY5MjMyMTB8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1527430253228-e93688616381?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwzfHx0b3klMjByb2JvdHxlbnwwfHx8fDE3NjY5MjMyMTB8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1527430253228-e93688616381?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwzfHx0b3klMjByb2JvdHxlbnwwfHx8fDE3NjY5MjMyMTB8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1527430253228-e93688616381?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwzfHx0b3klMjByb2JvdHxlbnwwfHx8fDE3NjY5MjMyMTB8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Photo by <a href="https://unsplash.com/@emilipothese">Emilipoth&#232;se</a> on <a href="https://unsplash.com">Unsplash</a></figcaption></figure></div><p>There are two ways of getting better and more specific output from an LLM: fine-tuning and retrieval-augmented generation (RAG). </p><p>Fine-tuning is like making a student study a curriculum for a long time. Over time, they internalize a worldview. Certain assumptions simply become &#8220;how things are.&#8221;</p><p>RAG is more like allowing the student to bring notes during an exam.</p><p>In one sense, the former is much more powerful. But in many real situations, the latter is more effective. If the question is narrow, and the relevant piece of knowledge is very specific, it often matters more that the right note is present in front of the student than that the student has spent years internalizing the entire curriculum.</p><p>Scanning a codebase, reading READMEs, and opening individual files is exactly this kind of exam-time context injection. When an AI does it, that <em>is</em> RAG, but obviously humans do it as well.</p><p>When an experienced engineer is introduced to a code base, their first task is not writing code. It is reconstructing local reality. They scan the architecture and attempt to infer conventions. But even after doing that, their first PR&#8217;s will need course-correcting by the other team members. They will have assumed that standards are followed and that visible patterns are intentional, both of which are frequently wrong. </p><p>Standards will not be followed because the developer who started something didn&#8217;t know about them or maybe they did but they decided to be clever, for good or bad reasons. And even clear patterns in the code base may reflect past decisions that were revised just last week.</p><p>The real rules live in commit history, Slack or email threads, half-remembered discussions, and the team&#8217;s collective memory. A human absorbs this over time. An AI doesn&#8217;t. Every session, it wakes up on day one. Now, in theory, fine-tuning would be the equivalent here, but at least with our current tooling, this isn&#8217;t an option.</p><h4>AI-Assisted Programming Is Not Delegation. It&#8217;s Context Construction.</h4><p>Given this perspective, how can we best work with LLM&#8217;s as software developers?</p><p>Well, we need to give the robot as much help as possible which means, somewhat paradoxically, adding more English to our code: comments, READMEs (I&#8217;m considering putting one in every folder), agent files with detailed descriptions.<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-1" href="#footnote-1" target="_self">1</a>  This creates an &#8220;exoskeleton&#8221; for the LLM to work with. Ask it to keep this up to date. </p><p>However, as tempting as it might be to document every idiosyncrasy of the codebase in an agent file, this will result in large prompts full of information, most of which will be irrelevant to most tasks, with a real risk of confusing the LLM. Even if the context window is large enough, you still want to be conscious of  what you put in there. In an uncanny, human-like fashion, the <a href="https://www.simplypsychology.org/primacy-recency.html">primacy and recency effects</a> where most attention is paid to the first and last bits of information presented have been <a href="https://arxiv.org/abs/2507.13949">observed</a> in LLM&#8217;s!</p><p>Thus, the primary job of the human engineer in an AI-assisted workflow is not to write code. It is to compile the world the code will be written inside.</p><p>For every non-trivial change, you have to decide:</p><ul><li><p>What assumptions about this part of the system is the LLM likely to make</p></li><li><p>Which of those assumptions are true and matter for this change</p></li><li><p>Which assumptions the model is likely to get wrong</p></li><li><p>And which slice of local reality must be injected to prevent that</p></li></ul><p>In other words: what would you need to tell a talented developer with zero institutional memory so that they can operate effectively?</p><p>You are not &#8220;using AI.&#8221; You are compiling context for non-persistent intelligence.</p><p>And that is now the real job. </p><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-1" href="#footnote-anchor-1" class="footnote-number" contenteditable="false" target="_self">1</a><div class="footnote-content"><p>Adding lots of English to your code is the antithesis to <em>Clean Code</em>. I was never a believer in that to begin with, but with LLM&#8217;s I am even less so. Some people will disagree.</p><p></p></div></div>]]></content:encoded></item><item><title><![CDATA[Schemalint Support for Triggers]]></title><description><![CDATA[Schemalint is a tool that inspects a PostgreSQL database and prints out errors if the schema diverges from a defined set of rules.]]></description><link>https://thoughts.kristiandupont.com/p/schemalint-support-for-triggers-85310f38eb6c</link><guid isPermaLink="false">https://thoughts.kristiandupont.com/p/schemalint-support-for-triggers-85310f38eb6c</guid><dc:creator><![CDATA[Kristian Dupont]]></dc:creator><pubDate>Sat, 24 May 2025 08:54:51 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/96b485ba-daf2-44aa-a3ea-a6010081c290_1024x682.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a href="https://www.npmjs.com/package/schemalint">Schemalint</a> is a tool that inspects a PostgreSQL database and prints out errors if the schema diverges from a defined set of&nbsp;rules.</p><p>I am <a href="https://kristiandupont.medium.com/are-you-using-types-when-you-should-be-linting-e9369404ef5b">big fan of linter rules</a> in general. They offer a nice way of establishing rules for your code base. This is handy both for aligning with team mates as well as your future self. With Schemalint, you can apply this to the database schema which is often a fundamental part of a software systems architecture.</p><p>The newest version adds support for triggers. I have been wanting this for a long time, because triggers are one of the more subtle parts of a schema and therefor arguable one of the more important aspects for a tool like this. A trigger (or the lack thereof) is not very visible in most database inspection tools, so it&#8217;s easy to miss when something is&nbsp;wrong.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!xfIJ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0d95a7d-45b9-4894-aa59-65108431760f_1024x682.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!xfIJ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0d95a7d-45b9-4894-aa59-65108431760f_1024x682.jpeg 424w, https://substackcdn.com/image/fetch/$s_!xfIJ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0d95a7d-45b9-4894-aa59-65108431760f_1024x682.jpeg 848w, https://substackcdn.com/image/fetch/$s_!xfIJ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0d95a7d-45b9-4894-aa59-65108431760f_1024x682.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!xfIJ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0d95a7d-45b9-4894-aa59-65108431760f_1024x682.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!xfIJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0d95a7d-45b9-4894-aa59-65108431760f_1024x682.jpeg" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a0d95a7d-45b9-4894-aa59-65108431760f_1024x682.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:null,&quot;width&quot;:null,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!xfIJ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0d95a7d-45b9-4894-aa59-65108431760f_1024x682.jpeg 424w, https://substackcdn.com/image/fetch/$s_!xfIJ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0d95a7d-45b9-4894-aa59-65108431760f_1024x682.jpeg 848w, https://substackcdn.com/image/fetch/$s_!xfIJ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0d95a7d-45b9-4894-aa59-65108431760f_1024x682.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!xfIJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0d95a7d-45b9-4894-aa59-65108431760f_1024x682.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div></div></div></a><figcaption class="image-caption">Photo by <a href="https://unsplash.com/@tsvetoslav?utm_source=medium&amp;utm_medium=referral">Tsvetoslav Hristov</a> on&nbsp;<a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure></div><p>For instance, I greatly prefer having a dedicated table and a trigger for soft deletes over having an archived_at column, as outlined in <a href="https://brandur.org/fragments/deleted-record-insert">this blog post</a>. I like this because the archived_at approach requires an extra is null condition in every single query you make to this table. And what&#8217;s worse: forgetting to do so often doesn&#8217;t look like a bug when developing. However, the dedicated table approach requires that you have the trigger in place and forgetting to set that is <em>also</em> easy to miss (though only needs to happen once per table). But now, Schemalint can catch those for&nbsp;you!</p><p>There is a custom <a href="https://github.com/kristiandupont/schemalint/blob/main/example/custom-rules/lastUpdated.js">example rule</a> for enforcing setting a last_update column in the examples folder. There is a triggers field on tables and views in the input to the rules (see the details&nbsp;<a href="https://kristiandupont.github.io/extract-pg-schema/api/extract-pg-schema.trigger.html">here</a>).</p>]]></content:encoded></item><item><title><![CDATA[The Pre-Contagion Window]]></title><description><![CDATA[When a new engineer starts on a team, something interesting happens.]]></description><link>https://thoughts.kristiandupont.com/p/the-pre-contagion-window-4ca5ec7bc968</link><guid isPermaLink="false">https://thoughts.kristiandupont.com/p/the-pre-contagion-window-4ca5ec7bc968</guid><dc:creator><![CDATA[Kristian Dupont]]></dc:creator><pubDate>Sat, 30 Nov 2024 10:21:44 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/75c63704-e812-409f-86e2-7331b15792f3_1024x1280.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>When a new engineer starts on a team, something interesting happens. Depending on their level of experience and outspokenness, they will note or perhaps complain about a lot of things. Processes, tools, conventions&#8202;&#8212;&#8202;details of engineering that differ from their previous&nbsp;roles.</p><p>In my experience, this lasts something like a month or two, and then it starts to die off. Think of this initial adjustment phase as the &#8216;pre-contagion window,&#8217; where a new hire hasn&#8217;t yet been infected by the habits&#8202;&#8212;&#8202;good or bad&#8202;&#8212;&#8202;of their new&nbsp;team.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!QupC!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F656b9287-43fb-4e23-9be4-57d8db5e6af7_1024x1280.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!QupC!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F656b9287-43fb-4e23-9be4-57d8db5e6af7_1024x1280.jpeg 424w, https://substackcdn.com/image/fetch/$s_!QupC!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F656b9287-43fb-4e23-9be4-57d8db5e6af7_1024x1280.jpeg 848w, https://substackcdn.com/image/fetch/$s_!QupC!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F656b9287-43fb-4e23-9be4-57d8db5e6af7_1024x1280.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!QupC!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F656b9287-43fb-4e23-9be4-57d8db5e6af7_1024x1280.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!QupC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F656b9287-43fb-4e23-9be4-57d8db5e6af7_1024x1280.jpeg" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/656b9287-43fb-4e23-9be4-57d8db5e6af7_1024x1280.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:null,&quot;width&quot;:null,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!QupC!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F656b9287-43fb-4e23-9be4-57d8db5e6af7_1024x1280.jpeg 424w, https://substackcdn.com/image/fetch/$s_!QupC!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F656b9287-43fb-4e23-9be4-57d8db5e6af7_1024x1280.jpeg 848w, https://substackcdn.com/image/fetch/$s_!QupC!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F656b9287-43fb-4e23-9be4-57d8db5e6af7_1024x1280.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!QupC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F656b9287-43fb-4e23-9be4-57d8db5e6af7_1024x1280.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div></div></div></a><figcaption class="image-caption">Photo by <a href="https://unsplash.com/@blakecheekk?utm_source=medium&amp;utm_medium=referral">Blake Cheek</a> on&nbsp;<a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure></div><p>This period of fresh perspective is fleeting, and if you&#8217;re not careful, it&#8217;s easy to squander.</p><h3>If you&#8217;re a manager or colleague to the new&nbsp;hire</h3><p>Make an effort to be extra patient with these complaints. First of all, they tend to go away. Yes, they may be things you&#8217;ve already considered, attempted, or found impractical for various reasons. It might also be the case that you&#8217;ve been doing some silly exercise so many times that you don&#8217;t see it anymore, even if it costs you minutes every day. If you find yourself dismissing the complaint due to competing priorities, consider whether addressing it might actually save time and effort in the long run. Are you too busy cutting down trees to sharpen the saw? Conversely, and this goes without saying, don&#8217;t tolerate toxic behavior from anyone, including new&nbsp;hires.</p><p>Consider asking new hires to document any issues they notice during their onboarding period.</p><h3>If you&#8217;re the new&nbsp;hire</h3><p>Perhaps show this blog post to your colleagues, as a sort of preemptive move to make them more patient with&nbsp;you.</p><p>Remember, you&#8217;re uniquely positioned to make impactful arguments. There is a lot more potency in suggestions that are backed up with real experience. If you suggest switching to tool X in order to solve problem Y and you can add that you did exactly this at your last job and the tradeoffs were worth it, that is a lot more convincing than someone just suggesting to try it and see what&nbsp;happens.</p><p>By recognizing the value of the pre-contagion window, you will continually improve your team&#8217;s processes.</p>]]></content:encoded></item><item><title><![CDATA[Bug-to-Error Distance]]></title><description><![CDATA[I hadn&#8217;t articulated this before, but I recently realized that I have an internal metric for assessing bugs and errors.]]></description><link>https://thoughts.kristiandupont.com/p/bug-to-error-distance-e25a0d607500</link><guid isPermaLink="false">https://thoughts.kristiandupont.com/p/bug-to-error-distance-e25a0d607500</guid><dc:creator><![CDATA[Kristian Dupont]]></dc:creator><pubDate>Tue, 21 May 2024 16:02:11 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/b4baf1ec-78ec-4d7a-af73-ce0aef86ae55_1024x683.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I hadn&#8217;t articulated this before, but I recently realized that I have an internal metric for assessing bugs and&nbsp;errors.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Dx72!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1365a448-f300-420a-9462-381f938a5bdf_1024x683.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Dx72!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1365a448-f300-420a-9462-381f938a5bdf_1024x683.jpeg 424w, https://substackcdn.com/image/fetch/$s_!Dx72!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1365a448-f300-420a-9462-381f938a5bdf_1024x683.jpeg 848w, https://substackcdn.com/image/fetch/$s_!Dx72!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1365a448-f300-420a-9462-381f938a5bdf_1024x683.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!Dx72!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1365a448-f300-420a-9462-381f938a5bdf_1024x683.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Dx72!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1365a448-f300-420a-9462-381f938a5bdf_1024x683.jpeg" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1365a448-f300-420a-9462-381f938a5bdf_1024x683.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:null,&quot;width&quot;:null,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!Dx72!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1365a448-f300-420a-9462-381f938a5bdf_1024x683.jpeg 424w, https://substackcdn.com/image/fetch/$s_!Dx72!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1365a448-f300-420a-9462-381f938a5bdf_1024x683.jpeg 848w, https://substackcdn.com/image/fetch/$s_!Dx72!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1365a448-f300-420a-9462-381f938a5bdf_1024x683.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!Dx72!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1365a448-f300-420a-9462-381f938a5bdf_1024x683.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div></div></div></a><figcaption class="image-caption">Photo by <a href="https://unsplash.com/@onun?utm_source=medium&amp;utm_medium=referral">Nuno Antunes</a> on&nbsp;<a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure></div><p>In this context, a <em>bug</em> is a piece of code that doesn&#8217;t do what the programmer intended, and an <em>error</em> is an undesired outcome or behavior.</p><p>Now, I am deliberately being vague about what <em>distance</em> refers to, as it&#8217;s a bit of a combination. It can mean &#8220;in the code&#8221;, measured in lines, files, folders, or services. It can also mean &#8220;in time&#8221;, where the visible error might appear significantly later than when the bug in question was triggered.</p><p>For syntax errors, this distance is practically zero. A squiggly line appears right underneath the bug. The error is both co-located and immediate, at least if you are working in a syntax-highlighting editor.</p><p>On the other hand, if you have a service that accidentally inserts garbage into your database, you might not discover it for a long time. The distance can be severe, both in terms of code location and time. This sort of bug is significantly harder to track down and&nbsp;fix.</p><p>So, it seems valuable to assess code not only on how potentially error-prone a pattern or piece of code is, but also on what the potential bug-to-error distance is, and try to minimize&nbsp;it.</p><p>How does one do that in practical terms? For instance, it has affected my view on type inference. For a while, my perspective was: <em>infer all the things!</em> Type safety without the plumbing code, best of both worlds&#8202;&#8212;&#8202;surely, that&#8217;s the way to go, always? Well, specifying types is like creating little valves in the code. &#8220;At this point, this is what I expect things to look like&#8221;. If you don&#8217;t do this, you will have less plumbing code but the price you pay is an increased bug-to-error distance because a piece of data may have a different shape than you expected, inferred from something &#8220;far away&#8221;.<br>I now tend to enable the <a href="https://typescript-eslint.io/rules/explicit-module-boundary-types/">explicit-module-boundary-types</a> linter rule when writing Typescript, which forces explicitly typed signatures for exported functions. I am sure there are plenty of other similar considerations that I haven&#8217;t thought of&nbsp;yet.</p>]]></content:encoded></item><item><title><![CDATA[Empathy, articulated]]></title><description><![CDATA[Like everyone and his brother, I&#8217;ve been working on a &#8220;coach&#8221; chat bot.]]></description><link>https://thoughts.kristiandupont.com/p/empathy-articulated-750a6601b122</link><guid isPermaLink="false">https://thoughts.kristiandupont.com/p/empathy-articulated-750a6601b122</guid><dc:creator><![CDATA[Kristian Dupont]]></dc:creator><pubDate>Sat, 21 Oct 2023 17:14:33 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/4f7e1982-5dfc-40c6-bfed-e08df7f99c1a_1024x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Like everyone and his brother, I&#8217;ve been working on a &#8220;coach&#8221; chat bot. Mostly for fun but also in an attempt to help me personally, primarily with health as I am in my 40s and need to take that stuff seriously. It&#8217;s not a product, I am just using it myself though I have a few friends playing with it as&nbsp;well.</p><p>If you haven&#8217;t worked with LLM&#8217;s as a developer, you might mistakenly think something similar to what I did: Hey, OpenAI has an API, it&#8217;s just ChatGPT without the UI! Chat is basically solved!</p><p>Well, it turns out that that is not quite the&nbsp;case.</p><p>Making an API call for completions feels more like using Mechanical Turk. You talk to it using natural language which is amazing, but it&#8217;s like every request is handled by someone new who doesn&#8217;t know anything, so you need to explain the whole thing from scratch every time. And the thing is: there is a hard limit to how long your description can be. So you need to summarize everything for it and figure out what context it needs to know for <em>this particular message</em>.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!7NxA!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9291b3f-3656-43c5-ac17-c4e400e5c331_1024x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!7NxA!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9291b3f-3656-43c5-ac17-c4e400e5c331_1024x1024.png 424w, https://substackcdn.com/image/fetch/$s_!7NxA!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9291b3f-3656-43c5-ac17-c4e400e5c331_1024x1024.png 848w, https://substackcdn.com/image/fetch/$s_!7NxA!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9291b3f-3656-43c5-ac17-c4e400e5c331_1024x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!7NxA!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9291b3f-3656-43c5-ac17-c4e400e5c331_1024x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!7NxA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9291b3f-3656-43c5-ac17-c4e400e5c331_1024x1024.png" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c9291b3f-3656-43c5-ac17-c4e400e5c331_1024x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:null,&quot;width&quot;:null,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!7NxA!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9291b3f-3656-43c5-ac17-c4e400e5c331_1024x1024.png 424w, https://substackcdn.com/image/fetch/$s_!7NxA!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9291b3f-3656-43c5-ac17-c4e400e5c331_1024x1024.png 848w, https://substackcdn.com/image/fetch/$s_!7NxA!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9291b3f-3656-43c5-ac17-c4e400e5c331_1024x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!7NxA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9291b3f-3656-43c5-ac17-c4e400e5c331_1024x1024.png 1456w" sizes="100vw" fetchpriority="high"></picture><div></div></div></a><figcaption class="image-caption">API Call</figcaption></figure></div><p>Imagine you received a letter out of the blue that said: &#8220;Well, at least I did 50 pushups today&#8221; and were expected to respond something. You don&#8217;t know who it&#8217;s from or even why they sent you this. What do you respond? Obviously, you can&#8217;t really say anything meaningful. There isn&#8217;t even a question in there! The job of the bot developer is to turn such a message into a piece of text that would enable you to not only respond something meaningful but also make it feel to the recipient as if you are just continuing a conversation that they were already having with&nbsp;you.</p><p>Establishing this context is very challenging. One common solution, at the moment, is <em>retrieval augmented generation</em>, or RAG, where you have a template that looks something like&nbsp;this:</p><blockquote><p>You are a helpful coach chat bot. Your purpose is to assist the user with health, wealth and well-being.</p></blockquote><blockquote><p>Here are some messages from the conversation that may or may not be related: <br>[[related-messages]]</p></blockquote><blockquote><p>Here are the ten most recent messages: <br>[[recent-history]]</p></blockquote><blockquote><p>User says:<br>[[message]]</p></blockquote><p>This provides a closer approximation to meaningful context, especially if one of those related messages tells you something else about pushups, like say, that the user has a goal of 50 per day for a month, or that they couldn&#8217;t do them because of an injury. Also, the most recent messages will make it clearer to you what you were talking about specifically, and what style of communication the two of you were using&#8202;&#8212;&#8202;was it a formal conversation, an inspirational pep-talk or perhaps more of a friendly bantering situation?</p><p>While fetching the most recent messages is straightforward, identifying the most relevant ones is anything but trivial. The first place to start might be to create <a href="https://en.wikipedia.org/wiki/Word_embedding">vector embeddings</a> out of every message in the history. Then you can find, say, the 10 messages with the highest cosine similarity to the incoming message. It will mean that previous messages that are <em>similar</em> will be inserted into the template, which is a place to start. Of course, similar is not the same as&nbsp;related.</p><p>I was using something close to this for a while and it did work but my bot felt quite distracted which was frustrating. Now, debugging this is <em>really hard</em>, but I suspect it&#8217;s because finding similar messages simply isn&#8217;t good enough. It wouldn&#8217;t remember old, related conversations if they weren&#8217;t sufficiently similar so it felt like I had to remind it of things constantly. It would apologize profusely and seem to recall when reminded, but that almost made it more annoying.</p><p>To address this, I implemented a strategy of tagging messages to create and utilize categories. That helped a bit but now my bot had developed dementia instead. It would often repeat points it had made in the past. That is also a seriously weird experience!</p><p>Another interesting thing is that since my bot communicates with the user at random times via the phone, I needed to tell it how long it was since the last interaction, what date, weekday and time of day it currently is, because otherwise it might say &#8220;good morning&#8221; in the evening, and it would carry every conversation as if there had been no delay. One thing I found here which seems quite intuitive when you think about it is that it was much better to tell it &#8220;last interaction was 3 days ago&#8221; instead of a specific date and then today&#8217;s date afterwards. It&#8217;s not great at math, so help it when you&nbsp;can!</p><p>I wanted the bot to not only react when the user initiates a chat. It should also reach out now and then. This isn&#8217;t something LLM&#8217;s will do automatically but a solution that seems to work well is quite simple: after each message, I ask the LLM for when it would like to follow up if it hasn&#8217;t heard from the user, and if so, what the reminder message should be. I then set a timer for the follow-up and re-initiate the chat with the reminder&nbsp;message.</p><p>But the hardest part, which is probably going to be the next uncanny valley for us to cross, is to convincingly &#8220;simulate&#8221; empathy. In order to make it feel like the bot cares about the user, it needs to be interested in them, learn about them and have a <a href="https://en.wikipedia.org/wiki/Theory_of_mind">theory of mind</a>. The latter is basically a way of saying that it should try to picture what the user is thinking and what their mood/mental state is&nbsp;like.</p><p>One thing I did which felt like a step in the right direction to me was this bit of text in the&nbsp;prompt:</p><blockquote><p>You should decide for each message if you are in &#8220;empathy&#8221; mode or &#8220;problem solving&#8221; mode. Don&#8217;t mix the two in one message.<br>In &#8220;empathy&#8221; mode, you are looking to understand and possibly to help the user understand. Ask questions. If ${member.name} seems incongruent or you are confused, ask for clarification. <br>In &#8220;problem solving&#8221; mode, you should offer solutions and suggestions. You can still ask questions but those will probably be of practical nature.</p></blockquote><p>Secondly, I have given the bot a &#8220;note pad&#8221;. It can add a note to this after each message. I then run two types of &#8220;dream cycles&#8221; where it reorganizes its thoughts. The server runs these asynchronously to the conversations. The simpler one runs daily. This one will make the bot read its notes, conversation and other inputs from today and update its note pad. Currently, the entire note pad is injected into every prompt which doesn&#8217;t scale that well, so I might look into a tagging system there as&nbsp;well.</p><p>The second dream cycle is weekly and more resource intensive. It will analyze various facets of the ongoing conversation, access its data, and perform multiple interpretation runs. For instance, it will try to spot what the users particular vocabulary is. If they say they &#8220;went for a run&#8221;, does that mean a 10 minute sprint or a 2 hour&nbsp;jog?</p><p>I studied neuroscience at university and while I always found it fascinating, it never felt as tangible and, well, <em>basic</em> as it does to me now. This is probably what is most exciting to me in all of&nbsp;this.</p><p>So the above changes have been about making it closer to human. But I want my bot to act as a <em>coach</em>. What makes a good coach? Well, one thing I intend to do is to give it a library. I might for example use my old collection on <a href="http://procrastotherapy.com/">Procrastotherapy</a> which has several good resources that I keep forgetting about. I imagine I will create little descriptions for each book/essay/video and then put those in my database as vector embeddings as well, so it can look those up. It would also be fun for it to keep up with new research by simply reading papers or articles posted to some subreddit or something. These are all vague ideas so&nbsp;far.</p><p>Bottom line is that programming LLM&#8217;s is <strong>fun</strong>! It&#8217;s a weird hybrid between programming, psychology, neuroscience and library science. It feels super empowering and humbling at the same time. Interestingly, I think it forces you to think in terms of empathy <em>yourself</em>: what does the bot know at this point? How can I articulate things so it will have the necessary basis for answering? When you think about it, this is a lot like what you do (or should do) when having a conversation with a fellow human being. That is just super fascinating to&nbsp;me.</p>]]></content:encoded></item><item><title><![CDATA[Meme-driven Retrospectives]]></title><description><![CDATA[I gave a talk at BestBrains Academy about cultural differences and using humor when leading teams of engineers.]]></description><link>https://thoughts.kristiandupont.com/p/meme-driven-retrospectives-eadf2b8aaa7e</link><guid isPermaLink="false">https://thoughts.kristiandupont.com/p/meme-driven-retrospectives-eadf2b8aaa7e</guid><dc:creator><![CDATA[Kristian Dupont]]></dc:creator><pubDate>Mon, 08 May 2023 06:55:21 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!gctQ!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0ac7c2c-b010-4699-996d-9a569881e34a_608x608.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I gave a talk at BestBrains Academy about cultural differences and using humor when leading teams of engineers.</p><p>&lt;a href="https://medium.com/media/29d020d306156aa817f80f5d7766ba71/href"&gt;https://medium.com/media/29d020d306156aa817f80f5d7766ba71/href&lt;/a&gt;</p><p>Here are some of the relevant&nbsp;links:</p><ul><li><p><a href="https://imgflip.com/memegenerator">Meme generator</a></p></li><li><p><a href="http://www.paulgraham.com/makersschedule.html">Maker&#8217;s Schedule, Manager&#8217;s Schedule</a></p></li><li><p><a href="https://heeris.id.au/2013/this-is-why-you-shouldnt-interrupt-a-programmer/">Why you shouldn&#8217;t interrupt a programmer (comic</a>)</p></li><li><p><a href="https://www.goodreads.com/book/show/97756.The_Joke_and_Its_Relation_to_the_Unconscious">The joke and its relation to the unconscious</a></p></li><li><p><a href="https://www.hofstede-insights.com/country-comparison-tool">Culture comparison tool</a></p></li></ul>]]></content:encoded></item><item><title><![CDATA[Are you using types when you should be linting?]]></title><description><![CDATA[I&#8217;m a big fan of static typing.]]></description><link>https://thoughts.kristiandupont.com/p/are-you-using-types-when-you-should-be-linting-e9369404ef5b</link><guid isPermaLink="false">https://thoughts.kristiandupont.com/p/are-you-using-types-when-you-should-be-linting-e9369404ef5b</guid><dc:creator><![CDATA[Kristian Dupont]]></dc:creator><pubDate>Sat, 06 May 2023 18:06:29 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/71a33619-2f0c-4178-9413-79d52f2cd759_1024x918.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I&#8217;m a big fan of static typing. With <a href="https://github.com/kristiandupont/kanel">Kanel</a>, I generate types for Typescript from Postgres databases. That means that the compiler helps me remember if a member has a fullName or firstName+ lastName columns/properties. If I mistype or mis-remember, a squiggly line in my editor or a CI failure stops me long before any user experiences an error. In a large system with many components, type checking saves me countless hours of&nbsp;work.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!fMMc!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad2bca94-df30-4f3a-8c47-f6751647ec58_1024x918.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!fMMc!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad2bca94-df30-4f3a-8c47-f6751647ec58_1024x918.png 424w, https://substackcdn.com/image/fetch/$s_!fMMc!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad2bca94-df30-4f3a-8c47-f6751647ec58_1024x918.png 848w, https://substackcdn.com/image/fetch/$s_!fMMc!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad2bca94-df30-4f3a-8c47-f6751647ec58_1024x918.png 1272w, https://substackcdn.com/image/fetch/$s_!fMMc!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad2bca94-df30-4f3a-8c47-f6751647ec58_1024x918.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!fMMc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad2bca94-df30-4f3a-8c47-f6751647ec58_1024x918.png" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ad2bca94-df30-4f3a-8c47-f6751647ec58_1024x918.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:null,&quot;width&quot;:null,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!fMMc!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad2bca94-df30-4f3a-8c47-f6751647ec58_1024x918.png 424w, https://substackcdn.com/image/fetch/$s_!fMMc!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad2bca94-df30-4f3a-8c47-f6751647ec58_1024x918.png 848w, https://substackcdn.com/image/fetch/$s_!fMMc!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad2bca94-df30-4f3a-8c47-f6751647ec58_1024x918.png 1272w, https://substackcdn.com/image/fetch/$s_!fMMc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad2bca94-df30-4f3a-8c47-f6751647ec58_1024x918.png 1456w" sizes="100vw" fetchpriority="high"></picture><div></div></div></a></figure></div><p>Using types to remember and enforce the shape of your data model is extremely valuable.</p><p>It doesn&#8217;t stop there, though. Type systems can be used to enforce a meta-architecture of sorts, which in many ways is even more powerful. Languages like Rust and Haskell excel at this. A common phrase among Haskellites is &#8220;when it compiles, it&nbsp;works&#8221;.</p><p>Let&#8217;s look at a trivially simple example. The most famous design pattern from the nineties was the mighty <em>singleton</em>. I don&#8217;t personally see it much these days but it probably sneaks in many places still. The idea is that there are some things you want one and only one instance of, like the connection to the database. And while <em>you</em> are perfectly capable of remembering this, your experience tells you that the developers you work with simply <em>cannot</em> be trusted not to create new instances, the little rascals, so you can nudge them in the right direction with this&nbsp;pattern.</p><p>What happens when you try to instantiate a singletonized class:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!7Zp1!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F40055994-1a90-427e-a935-9ddca6768c0f_1024x181.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!7Zp1!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F40055994-1a90-427e-a935-9ddca6768c0f_1024x181.png 424w, https://substackcdn.com/image/fetch/$s_!7Zp1!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F40055994-1a90-427e-a935-9ddca6768c0f_1024x181.png 848w, https://substackcdn.com/image/fetch/$s_!7Zp1!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F40055994-1a90-427e-a935-9ddca6768c0f_1024x181.png 1272w, https://substackcdn.com/image/fetch/$s_!7Zp1!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F40055994-1a90-427e-a935-9ddca6768c0f_1024x181.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!7Zp1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F40055994-1a90-427e-a935-9ddca6768c0f_1024x181.png" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/40055994-1a90-427e-a935-9ddca6768c0f_1024x181.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:null,&quot;width&quot;:null,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!7Zp1!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F40055994-1a90-427e-a935-9ddca6768c0f_1024x181.png 424w, https://substackcdn.com/image/fetch/$s_!7Zp1!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F40055994-1a90-427e-a935-9ddca6768c0f_1024x181.png 848w, https://substackcdn.com/image/fetch/$s_!7Zp1!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F40055994-1a90-427e-a935-9ddca6768c0f_1024x181.png 1272w, https://substackcdn.com/image/fetch/$s_!7Zp1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F40055994-1a90-427e-a935-9ddca6768c0f_1024x181.png 1456w" sizes="100vw"></picture><div></div></div></a></figure></div><p>Ah-HA! The type system saves the day, and your junior developer will realize that they are doing something they are not supposed to. The error message isn&#8217;t too helpful&#8202;&#8212;&#8202;it&#8217;s sort of like your mechanic telling you that there is insufficient electrical current flow between the car&#8217;s alternator, voltage regulator and starter motor, when all you need to know is that your battery is dead. But, with the singleton being such a common pattern, many developers will intuitively look for a static getInstance method or&nbsp;similar.</p><p>What we&#8217;re really doing with our complicated types is building a fixture for building our app. Or, if we&#8217;re making a library, it might be at an even higher level of abstraction. With higher-kinded types we might be enabling creating types for creating a certain architecture. Let me be completely clear that I am not arguing against typing, I just think that that it can and should be complemented with linting. In fact, I think that any project of significant size, private or public, should come with a set of custom linter&nbsp;rules.</p><p>The React <a href="https://legacy.reactjs.org/docs/hooks-rules.html">Rules of Hooks</a> is a nice example. The linter rules are extremely helpful when using hooks, and I cannot imagine the kind of trickery they would have had to pull if they wanted to achieve the same using&nbsp;types.</p><p>I am working on a library that synchronizes state from the database to the frontend in endpoints. Basically, if any mutation is made, the response should contain an array that describes those changes. For simple CRUD operations, this can be done automatically. But for instance, when a table has a trigger attached, there can be undetected updates which require &#8220;manual&#8221; updates. That is quite easy to forget, so I wanted to try and create a guard rail for&nbsp;this.</p><p>My first intuition was to use the type system. Maybe such mutations could return a state value that was marked as unresolved, and then a function that creates the necessary updates would take this state value and mark it as resolved. Endpoints would then have to return such a state value, if it was unresolved, it would trigger a compiler error. In Rust, I think this could be done quite elegantly because of its sophisticated ownership tracking. But I failed to come up with a nice solution in Typescript.</p><p>Instead, I wrote an ESLint rule. This is what the first version looked like, with some helper functions omitted:</p><pre><code>module.exports = {
  create: function (context) {
    return {
      CallExpression(node) {
        if (isCallExpression(node, "router", ["get", "post", "put", "patch", "delete"])) {
          if (node.arguments.length !== 2 || node.arguments[1].type !== "ArrowFunctionExpression") {
            return;
          }
          const knexCalls = countMethodCalls(node.arguments[1], "db", "knex");
          const resolveCalls = countMethodCalls(node.arguments[1], "db", "resolve");

          if (knexCalls !== resolveCalls) {
            context.report({
              node,
              message: `There must be a corresponding db.resolve call for each db.knex call.`,
            });
          }
        }
      },
    };
  },
};</code></pre><p>Basically, it checks that any db.knex call in an endpoint is complemented by a db.resolve call by making sure they are called the same number of times. I have since changed it quite significantly, but I wanted to show this version because it&#8217;s so trivial. If you are refraining from writing linter rules because it seems daunting, consider giving it a try. (Pro tip: ChatGPT or Co-Pilot are great at&nbsp;it!).</p><p>While this rule does not cover everything, it has the very nice feature that the error message tells you what the solution is. If this had been a type error, you would almost certainly be facing a cryptic message that only makes sense if you already know what&#8217;s wrong. With this solution, our architecture is both more self-validating and self-documenting. It sure beats looking in that eternally outdated wiki that we all edited after the big planning meeting a year and a half&nbsp;ago!</p><p>But, you might say, this doesn&#8217;t sound very <em><a href="https://papl.cs.brown.edu/2014/safety-soundness.html">sound</a>!</em> Indeed, it&#8216;s not. And I know that this is off-putting to some more than others. I personally don&#8217;t mind the lacking soundness in the Typescript type system, but to some it&#8217;s a complete deal-breaker. To me, the beauty is that linter rules are easy to disable in special conditions, so you really only do need to get them 80% right for them to be&nbsp;useful.</p><p>In my mind, the triad of typing, testing and linting should be the gold standard of writing solid&nbsp;code.</p>]]></content:encoded></item><item><title><![CDATA[Strapped to a Rocket: The Impact of AI]]></title><description><![CDATA[This is the third time I am writing about AI.]]></description><link>https://thoughts.kristiandupont.com/p/strapped-to-a-rocket-the-impact-of-ai-f5f175d43f22</link><guid isPermaLink="false">https://thoughts.kristiandupont.com/p/strapped-to-a-rocket-the-impact-of-ai-f5f175d43f22</guid><dc:creator><![CDATA[Kristian Dupont]]></dc:creator><pubDate>Thu, 06 Apr 2023 19:57:54 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!gctQ!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0ac7c2c-b010-4699-996d-9a569881e34a_608x608.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This is the third time I am writing about AI. I am not any kind of thought leader on the topic. I think like most people, I am trying to process what&#8217;s happening in every way possible, and writing is one such&nbsp;way.</p><p>My late grandmother was uncomfortable in this world in her old age. Even though she never experienced captivity, I think she might well have quoted Brooks in The Shawshank Redemption:</p><blockquote><p>&#8220;The world went and got itself in a big damn&nbsp;hurry&#8221;.</p></blockquote><p>&lt;a href="https://medium.com/media/c261bd401b5f20a78f49d2e22cb05645/href"&gt;https://medium.com/media/c261bd401b5f20a78f49d2e22cb05645/href&lt;/a&gt;</p><p>My grandmother thought the world of computers and the internet was <em>crazy</em>, not just silly but completely out of control. This was almost a decade ago. Although I empathized with her perspective, I saw the globalized world as a vast playground, brimming with opportunities and excitement. For me, technology and cheap flights (despite my climate-related concerns) were things to love. I was a digital nomad at the time and my life felt like one big adventure. Yes, things were changing fast but that change was almost always&nbsp;good!</p><p>Now, I am certainly getting older, but with everything that has happened in 2023 already (at the time of writing, <a href="https://github.com/microsoft/jarvis">Jarvis</a> and <a href="https://github.com/Torantulino/Auto-GPT">Auto-GPT</a> are the most recent, mind-blowing news that I am aware of), I am starting to feel a bit like Brooks or my grandmother. I have an instinctive desire to let this whole AI thing &#8220;settle&#8221;. I tell myself that it&#8217;s a shift, like the internet or smartphones, and like those times, things will be crazy for a bit but then we&#8217;ll adapt to the new reality and it becomes&nbsp;normal.</p><p>Tim Urban creates a great <a href="https://waitbutwhy.com/wop-excerpt">metaphor for human history</a>: a book. If all of human history was compressed into 1000 pages, the amount of progress that has happened on the very last page makes the entire rest of the book pale in comparison. You should read the blog post, it&#8217;s great like all of his writing. It really nails the level of exponential that we&#8217;re going through at this point in&nbsp;time.</p><p>The thing about exponential growth is that it&#8217;s hard to grasp even when you understand it. The <a href="https://en.wikipedia.org/wiki/Wheat_and_chessboard_problem">Wheat and chessboard problem</a> points out the simple fact that if you start with one grain of wheat and double it 64 times, you end up with 2^64&#8722;1 grains, an unspeakable amount of wheat. I know this, intellectually. And yet, if I just try to picture a chess board where each square has double the amount of the previous one, and the first square has merely one single grain of wheat, my mind <em>insists</em> that you would end up with just a pile of wheat. Something maybe in the range of thousands of&nbsp;grains.</p><p>If we are indeed experiencing the ignition of an &#8220;intelligence explosion&#8221;, we aren&#8217;t (well I certainly am not) capable of grasping it. Tim Urban predicts that the world will either enter a state of utopia where just about every problem is solved, or it will result in our extinction. I think he might be right, but I also think that until one of those things happen, we are going to feel like we&#8217;ve been strapped to a rocket, just trying to hold&nbsp;on.</p>]]></content:encoded></item><item><title><![CDATA[Use 98% TailwindCSS, 2% plain CSS]]></title><description><![CDATA[Yes, it&#8217;s awesome.]]></description><link>https://thoughts.kristiandupont.com/p/tailwindcss-an-update-b88e88c8a7b9</link><guid isPermaLink="false">https://thoughts.kristiandupont.com/p/tailwindcss-an-update-b88e88c8a7b9</guid><dc:creator><![CDATA[Kristian Dupont]]></dc:creator><pubDate>Tue, 21 Mar 2023 16:29:49 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/48697618-02b8-4175-abe1-f8867dcfc95a_620x497.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Yes, it&#8217;s awesome. I declared my affection a while ago and it has genuinely altered the way I write frontend code. <a href="https://itnext.io/and-naming-things-tailwind-css-typescript-and-mammals-9eab459633d2">Everything I loved about it</a>, I still love. Additionally, it has nudged me out of the habit of making CSS rules that cascade. While those certainly have their advantages, avoiding them means that you can move html around without any fear which has felt very liberating to&nbsp;me.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!GGn-!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffd4df4ae-cf20-4a42-83e2-2df7703b1130_620x497.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!GGn-!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffd4df4ae-cf20-4a42-83e2-2df7703b1130_620x497.jpeg 424w, https://substackcdn.com/image/fetch/$s_!GGn-!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffd4df4ae-cf20-4a42-83e2-2df7703b1130_620x497.jpeg 848w, https://substackcdn.com/image/fetch/$s_!GGn-!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffd4df4ae-cf20-4a42-83e2-2df7703b1130_620x497.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!GGn-!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffd4df4ae-cf20-4a42-83e2-2df7703b1130_620x497.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!GGn-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffd4df4ae-cf20-4a42-83e2-2df7703b1130_620x497.jpeg" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/fd4df4ae-cf20-4a42-83e2-2df7703b1130_620x497.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:null,&quot;width&quot;:null,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!GGn-!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffd4df4ae-cf20-4a42-83e2-2df7703b1130_620x497.jpeg 424w, https://substackcdn.com/image/fetch/$s_!GGn-!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffd4df4ae-cf20-4a42-83e2-2df7703b1130_620x497.jpeg 848w, https://substackcdn.com/image/fetch/$s_!GGn-!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffd4df4ae-cf20-4a42-83e2-2df7703b1130_620x497.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!GGn-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffd4df4ae-cf20-4a42-83e2-2df7703b1130_620x497.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div></div></div></a></figure></div><p>Software development can be fickle like the fashion industry and right now, utility classes are <em>on fire</em>. I suspect that in time, they will fade a bit, the way micro services or NoSQL databases have. We have a tendency as a community to become infatuated with new principles, believing that they are a universal solution that must be applied everywhere, to everything.</p><p>So, while I can attest to the many benefits of utility classes, I find that there are a few downsides worth pointing&nbsp;out.</p><p>When a designer hands me a Figma file, utility classes are great because I can easily identify the ones that match the margin, padding etc. that I am looking at. It&#8217;s a lot easier than scanning through CSS files, looking for the class that matches everything, if a such even exists. <em>However</em>, it is also easy to overlook subtle details like letter spacing and line height. Those are things my non-designer eye is more likely to overlook and since there isn&#8217;t a rule set once and for all, I need to actively remember&nbsp;them.</p><p>The same applies to classes for pseudo-states like hover and disabled. The fact that you always have to specify everything makes this particularly easy to miss as you can quickly look at the result and not notice that anything is missing, unless you explicitly try out those&nbsp;states.</p><p>Tailwind recommends extracting components in React or whatever your framework is to encapsulate styles, rather than CSS classes. This generally seems to me to be a sound principle. Nevertheless, there are situations where it can be frustrating to have to split code into atoms, especially if you have a strict one-component-per-file policy (which I don&#8217;t recommend, though!)</p><p>Finally, there are a number of things that I really just want to set and forget. For instance, I generally want all my links to look the same. Having to add a bunch of classes to each one feels unnecessary and silly, as does creating a component.</p><p>Overall, Tailwind is a fantastic framework that I will continue to use. I have just reached the personal conclusion that there is <em>absolutely</em> nothing wrong with creating a&nbsp;.css file or two to complement it (and to be clear, I don&#8217;t think the Tailwind people would say otherwise!) You can use <a href="https://tailwindcss.com/docs/reusing-styles#extracting-classes-with-apply">apply</a> or you can just make plain old styles, it&#8217;s all good. Just don&#8217;t be dogmatic for the sake of&nbsp;dogma.</p>]]></content:encoded></item><item><title><![CDATA[I interviewed my dad]]></title><description><![CDATA[..and I encourage you to do the same!]]></description><link>https://thoughts.kristiandupont.com/p/i-interviewed-my-dad-1621f8a4b5a9</link><guid isPermaLink="false">https://thoughts.kristiandupont.com/p/i-interviewed-my-dad-1621f8a4b5a9</guid><dc:creator><![CDATA[Kristian Dupont]]></dc:creator><pubDate>Mon, 30 Jan 2023 09:26:41 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/c31729bf-567b-460b-8477-6b93a6929340_1024x576.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>..and I encourage you to do the&nbsp;same!</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!UuKf!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21510d70-9c8a-476c-8f7f-aeb07e57bde5_1024x576.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!UuKf!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21510d70-9c8a-476c-8f7f-aeb07e57bde5_1024x576.png 424w, https://substackcdn.com/image/fetch/$s_!UuKf!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21510d70-9c8a-476c-8f7f-aeb07e57bde5_1024x576.png 848w, https://substackcdn.com/image/fetch/$s_!UuKf!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21510d70-9c8a-476c-8f7f-aeb07e57bde5_1024x576.png 1272w, https://substackcdn.com/image/fetch/$s_!UuKf!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21510d70-9c8a-476c-8f7f-aeb07e57bde5_1024x576.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!UuKf!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21510d70-9c8a-476c-8f7f-aeb07e57bde5_1024x576.png" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/21510d70-9c8a-476c-8f7f-aeb07e57bde5_1024x576.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:null,&quot;width&quot;:null,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!UuKf!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21510d70-9c8a-476c-8f7f-aeb07e57bde5_1024x576.png 424w, https://substackcdn.com/image/fetch/$s_!UuKf!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21510d70-9c8a-476c-8f7f-aeb07e57bde5_1024x576.png 848w, https://substackcdn.com/image/fetch/$s_!UuKf!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21510d70-9c8a-476c-8f7f-aeb07e57bde5_1024x576.png 1272w, https://substackcdn.com/image/fetch/$s_!UuKf!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21510d70-9c8a-476c-8f7f-aeb07e57bde5_1024x576.png 1456w" sizes="100vw" fetchpriority="high"></picture><div></div></div></a></figure></div><p>My dad is a man of fierce integrity. If you have a principle you should stand by it, also when it&#8217;s not convenient for you. He also has a very big heart. He holds himself to high standards but never lectures others. He is in many ways my hero, even though he would probably consider such a sentiment to be &#8220;noget amerikansk pjat&#8221; (American babble).</p><p>He is 80 years old and my son is 1, so there is a risk that the two won&#8217;t get to know each other well. This gave me the idea to set up an &#8220;interview&#8221; with him. Very informal and casual, I wanted it to feel like a regular conversation but where I ask him about his childhood, life and perspectives in general.<br>This turned out not just to be a very enjoyable experience but also, I learned a lot. There was so much that I didn&#8217;t know, about growing up in the Danish country side just after WWII, about his friends and extended family and, in turn, about my own childhood.<br>I now have a few hours of material and we&#8217;re just reaching his teens. I am really excited about this and I am hitting myself for not doing it sooner, and with more people. What I wouldn&#8217;t give to have such footage with my mom who passed away two years ago! Or even close friends, or my brother, from 20 years ago! I need to make a habit out of&nbsp;this.</p><p>I will never share these videos with anyone other than a few agreed-upon exceptions. I want to make sure it feels intimate and real and I don&#8217;t want anyone to be watching their words. The purpose is not to make &#8220;content&#8221; or anything, it&#8217;s to preserve a snapshot of something precious that will some day disappear.</p><p>I decided to write this post because I honestly think it&#8217;s something many people should consider doing in some form with their loved ones. Maybe in a similar setup, or maybe in a variation. I would love to hear alternative or complementary ideas.</p>]]></content:encoded></item><item><title><![CDATA[Beyond The Kaleidoscope]]></title><description><![CDATA[Generated content has been around for ages.]]></description><link>https://thoughts.kristiandupont.com/p/beyond-the-kaleidoscope-a43009f13ccf</link><guid isPermaLink="false">https://thoughts.kristiandupont.com/p/beyond-the-kaleidoscope-a43009f13ccf</guid><dc:creator><![CDATA[Kristian Dupont]]></dc:creator><pubDate>Wed, 04 Jan 2023 19:43:31 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/d9b1ba5e-8596-48ba-822c-4acc04215126_1024x679.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Generated content has been around for ages. I remember dreaming of video games with unlimited territory to explore, with the obvious solution being for the terrain to be generated by some clever algorithm. As it turned out, every time I actually played a game where something was generated, the experience was consistently underwhelming. <a href="https://www.spore.com/">Spore</a> was supposed to be the ultimate in terms of emergence, and it was&nbsp;boring!</p><p>I struggled to pinpoint what exactly was lacking. Yes, generated content was not &#8220;human&#8221;, whatever that means, but neither is the real world&#8202;&#8212;&#8202;nature and the shape of our planet is the result of a bunch of random events (unless you believe in intelligent design I guess, in which case this whole discussion must take on a new level of semantics).</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!rsu1!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c2ad2d4-741e-4310-8dac-b70c39c7aef4_1024x679.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!rsu1!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c2ad2d4-741e-4310-8dac-b70c39c7aef4_1024x679.jpeg 424w, https://substackcdn.com/image/fetch/$s_!rsu1!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c2ad2d4-741e-4310-8dac-b70c39c7aef4_1024x679.jpeg 848w, https://substackcdn.com/image/fetch/$s_!rsu1!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c2ad2d4-741e-4310-8dac-b70c39c7aef4_1024x679.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!rsu1!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c2ad2d4-741e-4310-8dac-b70c39c7aef4_1024x679.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!rsu1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c2ad2d4-741e-4310-8dac-b70c39c7aef4_1024x679.jpeg" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/3c2ad2d4-741e-4310-8dac-b70c39c7aef4_1024x679.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:null,&quot;width&quot;:null,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!rsu1!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c2ad2d4-741e-4310-8dac-b70c39c7aef4_1024x679.jpeg 424w, https://substackcdn.com/image/fetch/$s_!rsu1!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c2ad2d4-741e-4310-8dac-b70c39c7aef4_1024x679.jpeg 848w, https://substackcdn.com/image/fetch/$s_!rsu1!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c2ad2d4-741e-4310-8dac-b70c39c7aef4_1024x679.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!rsu1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c2ad2d4-741e-4310-8dac-b70c39c7aef4_1024x679.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div></div></div></a><figcaption class="image-caption">Photo by <a href="https://unsplash.com/es/@mlightbody?utm_source=medium&amp;utm_medium=referral">Malcolm Lightbody</a> on&nbsp;<a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure></div><p>One distinct problem was that even though the generated content was unpredictable in the specifics, you would always detect patterns. The way looking in a kaleidoscope theoretically offers unlimited visuals but still gets boring quickly. But even when such patterns were hard to detect, the mere fact that there was no intent behind anything just made it feel pointless. Which I find interesting philosophically, because why would a made-up but equally unreal world be any less &#8220;pointless&#8221;?</p><p>I think what has me so stunned by the advances in AI that we saw in 2022 is that this limitation seems to have been overcome. The output from various AI generators doesn&#8217;t seem <em>pointless</em> to me. ChatGPT writes fiction where I <em>want </em>to follow, I am curious about what it comes up with. In spite of it not being sentient, not knowing anything about Australia, math or <em>wh</em>y<em> </em>these texts are funny, I find them to be genuinely entertaining:</p><h3>Elad Richardson on Twitter: "So impressive &#129432; pic.twitter.com/ng61A0ch02 / Twitter"</h3><p>So impressive &#129432; pic.twitter.com/ng61A0ch02</p><p><em>(Incidentally, John Cleese gave a great <a href="https://www.youtube.com/watch?v=Pb5oIIPO62g">talk on creativity</a> which it seems ChatGPT has&nbsp;watched)</em></p><p>Similarly, the image generators create stuff that triggers emotions in me the way looking at (human-made) art does. I have yet to experience this from a music generator but I suspect I will before the end of 2023. One of the strongest indicators of the shift, to me, is that we now seem to be discussing what art is and isn&#8217;t. It reminds me of the discussions that appeared when Bitcoin was created about what money is and&nbsp;isn&#8217;t.</p><p>The thing is, all of this simply illustrates <a href="https://en.wikipedia.org/wiki/Moravec's_paradox">Moravec&#8217;s Paradox</a> which was well known years ago. For some reason, I still believed that the progression of automation would go: <em>blue collar &#8594; white collar &#8594; executive &#8594; creative</em>, with the last step being a possible &#8220;never&#8221;. Turns out that was where it started. While I don&#8217;t believe we&#8217;re at the singularity yet (the tools that generate code are mind blowing but still quite helpless and certainly not capable of creating more advanced AI themselves), it does feel like the world has been turned upside&nbsp;down.</p>]]></content:encoded></item><item><title><![CDATA[About index.js Files..]]></title><description><![CDATA[Index.js files are a &#8220;cute&#8221; feature that Ryan Dahl came up with when he designed Node.js. While he officially regrets them, I think they&#8230;]]></description><link>https://thoughts.kristiandupont.com/p/about-index-js-files-93c9928cecfd</link><guid isPermaLink="false">https://thoughts.kristiandupont.com/p/about-index-js-files-93c9928cecfd</guid><dc:creator><![CDATA[Kristian Dupont]]></dc:creator><pubDate>Tue, 20 Dec 2022 08:44:13 GMT</pubDate><enclosure url="https://cdn-images-1.medium.com/max/800/0*fn6tny8A_XGf82vf" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Index.js files are a &#8220;cute&#8221; feature that Ryan Dahl came up with when he designed Node.js. While he <a href="https://www.youtube.com/watch?v=M3BM9TB-8yA">officially regrets them</a>, I think they have evolved to be a useful tool for structuring code. In this post, I will try to articulate the rules that I had made up for myself about them.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://cdn-images-1.medium.com/max/800/0*fn6tny8A_XGf82vf" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://cdn-images-1.medium.com/max/800/0*fn6tny8A_XGf82vf 424w, https://cdn-images-1.medium.com/max/800/0*fn6tny8A_XGf82vf 848w, https://cdn-images-1.medium.com/max/800/0*fn6tny8A_XGf82vf 1272w, https://cdn-images-1.medium.com/max/800/0*fn6tny8A_XGf82vf 1456w" sizes="100vw"><img src="https://cdn-images-1.medium.com/max/800/0*fn6tny8A_XGf82vf" data-attrs="{&quot;src&quot;:&quot;https://cdn-images-1.medium.com/max/800/0*fn6tny8A_XGf82vf&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:null,&quot;width&quot;:null,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://cdn-images-1.medium.com/max/800/0*fn6tny8A_XGf82vf 424w, https://cdn-images-1.medium.com/max/800/0*fn6tny8A_XGf82vf 848w, https://cdn-images-1.medium.com/max/800/0*fn6tny8A_XGf82vf 1272w, https://cdn-images-1.medium.com/max/800/0*fn6tny8A_XGf82vf 1456w" sizes="100vw" fetchpriority="high"></picture><div></div></div></a><figcaption class="image-caption">Photo by <a href="https://unsplash.com/@qwitka?utm_source=medium&amp;utm_medium=referral">Maksym Kaharlytskyi</a> on&nbsp;<a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure></div><p>Basically<em>, </em>index files serve one of two purposes: as <em>encapsulation </em>or as a <em>namespace</em>. The corollary of this is that index files act as guard rails for imports&#8202;&#8212;&#8202;I avoid importing something from a subfolder of a folder containing an index file.</p><h4>Encapsulation</h4><p>If an index file is used for encapsulation, it should contain a default export for the primary entity (class, component, function, etc.) in the folder. It may also contain a number of named exports for related, &#8220;secondary&#8221; items. This is useful when you want to hide the implementation details of a module and expose only a single interface. For example, consider the following folder structure:</p><pre><code>src
&#9492;&#9472;&#9472; TopBar
    &#9500;&#9472;&#9472; index.js
    &#9500;&#9472;&#9472; TopBar.js
    &#9500;&#9472;&#9472; DropDown.js
    &#9492;&#9472;&#9472; SearchBox.js</code></pre><p>In this case, the <code>TopBar</code> folder contains the implementation for a top bar component, including a few sub-components. The index file re-exports the component in TopBar.js:</p><pre><code>// index.js
export { default } from './TopBar';</code></pre><p>To use the component, you would import the default export from the <code>TopBar</code> folder:</p><pre><code>import TopBar from './TopBar';</code></pre><h4>Namespace</h4><p>If an index file is used as a namespace, it should contain a bunch of named exports for everything in the folder. I used to do this a lot but I have actually stopped doing it in favor of just having the folder name in the import. However, it can still be useful to group related entities under a single identifier if they feel particularly strongly related.</p><p>For example, consider the following folder structure:</p><pre><code>src
&#9492;&#9472;&#9472; shared-components
    &#9500;&#9472;&#9472; LoadingButton.jsx
    &#9500;&#9472;&#9472; Spinner.jsx
    &#9492;&#9472;&#9472; ModalDialog
        &#9500;&#9472;&#9472; ModalDialog.jsx
        &#9500;&#9472;&#9472; index.js
        &#9492;&#9472;&#9472; Overlay.jsx
    &#9500;&#9472;&#9472; Chevron.jsx
    &#9492;&#9472;&#9472; index.js</code></pre><p>In this case, the <code>shared-components</code> folder contains four components. The index.js file in the</p><pre><code>export { default as LoadingButton } from './LoadingButton';
export { default as Spinner } from './Spinner';
export { default as ModalDialog } from './ModalDialog';
export { default as Chevron } from './Chevron';</code></pre><p>This makes <code>shared-components</code> work like a sort of package that you can import the components from. The win over importing the components from their respective sub-directories is small but might be to your preference.</p><h4>Importing from Sub-folders</h4><p>If a folder has an index file, I consider that a guard rail which should prevent me from importing anything from sub-folders. Now, files that are in the folder or a sub-folder <em>themselves </em>are allowed to import stuff, you just shouldn&#8217;t cross the boundary, so to speak. This is something I want to create a linter rule for because it could be enforced automatically, but for now I keep track of it manually.</p><h4>Emergent Design</h4><p>Both use cases are well suited for emergent design.</p><p>In the case of encapsulation, you can start by writing something in a single file. When that file becomes too large or complex, you replace it with a folder of the same name (sans extension), and from the perspective of the rest of the code, everything remains the same.</p><p>When used for namespaces, this is a nice first step for grouping related code before moving it into a separate package, either in your mono-repo using workspaces or all the way to NPM, depending on how general-purpose it becomes.</p><h4>..default exports??</h4><p>Some people argue that the concept of default exports was a mistake and I have some sympathy for that. At this point I&#8217;ve used them so much that it&#8217;s basically muscle memory and I&#8217;ve found that VSCode has sufficient refactoring capabilities for handling them, to the point where the downsides are negligible.</p>]]></content:encoded></item><item><title><![CDATA[Name is the Leftmost Part of Semver]]></title><description><![CDATA[Sorry, this is a bit of a rant. My preferred routing library for React has been Raviger for a while but recently I needed to figure out how&#8230;]]></description><link>https://thoughts.kristiandupont.com/p/name-is-the-leftmost-part-of-semver-f6cd7b3f368</link><guid isPermaLink="false">https://thoughts.kristiandupont.com/p/name-is-the-leftmost-part-of-semver-f6cd7b3f368</guid><dc:creator><![CDATA[Kristian Dupont]]></dc:creator><pubDate>Sat, 24 Sep 2022 07:18:22 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/d241738e-eaba-4767-9b36-3fe25916ee25_800x352.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!mXan!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F804a26a3-e850-4fae-afb7-1509dcdacef8_800x352.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!mXan!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F804a26a3-e850-4fae-afb7-1509dcdacef8_800x352.png 424w, https://substackcdn.com/image/fetch/$s_!mXan!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F804a26a3-e850-4fae-afb7-1509dcdacef8_800x352.png 848w, https://substackcdn.com/image/fetch/$s_!mXan!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F804a26a3-e850-4fae-afb7-1509dcdacef8_800x352.png 1272w, https://substackcdn.com/image/fetch/$s_!mXan!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F804a26a3-e850-4fae-afb7-1509dcdacef8_800x352.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!mXan!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F804a26a3-e850-4fae-afb7-1509dcdacef8_800x352.png" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/804a26a3-e850-4fae-afb7-1509dcdacef8_800x352.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:null,&quot;width&quot;:null,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!mXan!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F804a26a3-e850-4fae-afb7-1509dcdacef8_800x352.png 424w, https://substackcdn.com/image/fetch/$s_!mXan!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F804a26a3-e850-4fae-afb7-1509dcdacef8_800x352.png 848w, https://substackcdn.com/image/fetch/$s_!mXan!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F804a26a3-e850-4fae-afb7-1509dcdacef8_800x352.png 1272w, https://substackcdn.com/image/fetch/$s_!mXan!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F804a26a3-e850-4fae-afb7-1509dcdacef8_800x352.png 1456w" sizes="100vw" fetchpriority="high"></picture><div></div></div></a></figure></div><p>Sorry, this is a bit of a rant. My preferred routing library for React has been <a href="https://kyeotic.github.io/raviger/">Raviger</a> for a while but recently I needed to figure out how the most recent version of React Router works. It turns out that there are plenty of answers on StackOverflow and elsewhere that explain how to do things, many for outdated versions of the API.</p><p>I know that the React Router developers have taken flak for changing things throughout the years and I don&#8217;t mean to single them out. They have created great value for the community which I have capitalized on for free. I am grateful and they owe me absolutely nothing.</p><p>The small point I want to make, though, is this: if you are changing your API so much that it&#8217;s not just a &#8220;major breaking change&#8221;, but rather a complete redesign, consider changing the name of your library rather than just the version. For ecosystem related reasons as well as pedantic ones.</p>]]></content:encoded></item><item><title><![CDATA[Hyperbolic Growth]]></title><description><![CDATA[15 years ago I felt like the world was improving at record speeds because of technological advances. And I was a part of it. Not that I did&#8230;]]></description><link>https://thoughts.kristiandupont.com/p/hyperbolic-growth-df689a6eb4b5</link><guid isPermaLink="false">https://thoughts.kristiandupont.com/p/hyperbolic-growth-df689a6eb4b5</guid><dc:creator><![CDATA[Kristian Dupont]]></dc:creator><pubDate>Sat, 17 Sep 2022 11:47:58 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/ebc30570-a97c-45b4-8564-ee92dcebab8c_800x800.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!h7U4!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7bacdbea-6f26-4941-bd9e-481b8c084766_800x800.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!h7U4!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7bacdbea-6f26-4941-bd9e-481b8c084766_800x800.png 424w, https://substackcdn.com/image/fetch/$s_!h7U4!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7bacdbea-6f26-4941-bd9e-481b8c084766_800x800.png 848w, https://substackcdn.com/image/fetch/$s_!h7U4!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7bacdbea-6f26-4941-bd9e-481b8c084766_800x800.png 1272w, https://substackcdn.com/image/fetch/$s_!h7U4!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7bacdbea-6f26-4941-bd9e-481b8c084766_800x800.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!h7U4!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7bacdbea-6f26-4941-bd9e-481b8c084766_800x800.png" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7bacdbea-6f26-4941-bd9e-481b8c084766_800x800.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:null,&quot;width&quot;:null,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!h7U4!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7bacdbea-6f26-4941-bd9e-481b8c084766_800x800.png 424w, https://substackcdn.com/image/fetch/$s_!h7U4!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7bacdbea-6f26-4941-bd9e-481b8c084766_800x800.png 848w, https://substackcdn.com/image/fetch/$s_!h7U4!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7bacdbea-6f26-4941-bd9e-481b8c084766_800x800.png 1272w, https://substackcdn.com/image/fetch/$s_!h7U4!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7bacdbea-6f26-4941-bd9e-481b8c084766_800x800.png 1456w" sizes="100vw" fetchpriority="high"></picture><div></div></div></a></figure></div><p>15 years ago I felt like the world was improving at record speeds because of technological advances. And I was a part of it. Not that I did anything particularly influential, but I identified with the tech sector and it felt like &#8220;my people&#8221; were freeing the world of the old, evil corporations that were rife with nepotism and corruption, helping it turn more meritocratic and equal. I felt genuinely optimistic about where the world was headed and I was always excited about products launches, software or hardware.</p><p>Today they make me anxious.</p><p>Not only because the concept of &#8220;startups&#8221; no longer feels like the breath of fresh air it used to, but more like a parody of itself. As Paul Graham became wealthy it seems he also became convinced that the only thing that motivates anyone is wealth, sounding more and more like those corporatists of yesteryear that were happy with the status quo. But while I find that disappointing, it&#8217;s not what this post is about. I am more uneasy about the technology itself, given the speed we are advancing at.</p><p>Two products have affected me in particular: <a href="https://openai.com/dall-e-2/">DALL-E-2</a> and <a href="https://github.com/features/copilot">GitHub Copilot</a>. They are both mind blowing to me and have given me this sinking feeling of technology moving not only really fast, but faster than I thought possible. And while still fun, I am starting to feel that the tech industry that I used to be proud of belonging to is like having a pet tiger that was cute (and a bit cool) as a cub but is suddenly becoming hard to control and I can&#8217;t really do anything about it.</p><div class="captioned-image-container"><figure><div id="youtube2-HOPwXNFU7oU" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;HOPwXNFU7oU&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/HOPwXNFU7oU?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><figcaption class="image-caption">Das Rad&#8202;&#8212;&#8202;Oscar nominted short film from 2001</figcaption></figure></div><p>I guess I am experiencing a threat similar to what so many professions before mine must have felt as technological advances made them irrelevant. Maybe I simply belong on /r/leopardsatemyface. Now, software development is not going to disappear any time soon. Maybe not in 15 years or maybe even in my lifetime. But just like the two rocks in the short film Das Rad (embedded above&#8202;&#8212;&#8202;if you haven&#8217;t, I highly recommend watching it, it&#8217;s just 8 minutes long), it feels like we&#8217;re past a sort of tipping point of hyperbolic growth. The only thing I am certain of now is that I have no idea what will happen.</p><p>I am not particularly worried about my own financial well being. I <em>think </em>I will be fine. I do worry slightly about my sense of self. Creativity is a big part of what I feel defines me, and if that loses all value then it will surely affect me. Then, there are obviously a number of Black Mirror-esque dystopias that don&#8217;t seem all that unrealistic any more. But mostly I struggle with how to guide my son when he grows up. In that regard I feel truly lost. What will be a viable career in 20 years? Will the concept of a &#8220;career&#8221; even make sense by then? I have no idea, and I am uncomfortable with that.</p>]]></content:encoded></item><item><title><![CDATA[E2E type safety with tRPC]]></title><description><![CDATA[I gave a talk at Copenhagen React about the tRPC framework and how we use it instead of REST or GraphQL at Mymee:]]></description><link>https://thoughts.kristiandupont.com/p/e2e-type-safety-with-trpc-50fcbbf55206</link><guid isPermaLink="false">https://thoughts.kristiandupont.com/p/e2e-type-safety-with-trpc-50fcbbf55206</guid><dc:creator><![CDATA[Kristian Dupont]]></dc:creator><pubDate>Sun, 24 Apr 2022 08:46:15 GMT</pubDate><enclosure url="https://cdn-images-1.medium.com/max/800/1*SC2C3yHN7k_CkAVpl0fiJw.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://cdn-images-1.medium.com/max/800/1*SC2C3yHN7k_CkAVpl0fiJw.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://cdn-images-1.medium.com/max/800/1*SC2C3yHN7k_CkAVpl0fiJw.png 424w, https://cdn-images-1.medium.com/max/800/1*SC2C3yHN7k_CkAVpl0fiJw.png 848w, https://cdn-images-1.medium.com/max/800/1*SC2C3yHN7k_CkAVpl0fiJw.png 1272w, https://cdn-images-1.medium.com/max/800/1*SC2C3yHN7k_CkAVpl0fiJw.png 1456w" sizes="100vw"><img src="https://cdn-images-1.medium.com/max/800/1*SC2C3yHN7k_CkAVpl0fiJw.png" data-attrs="{&quot;src&quot;:&quot;https://cdn-images-1.medium.com/max/800/1*SC2C3yHN7k_CkAVpl0fiJw.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:null,&quot;width&quot;:null,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://cdn-images-1.medium.com/max/800/1*SC2C3yHN7k_CkAVpl0fiJw.png 424w, https://cdn-images-1.medium.com/max/800/1*SC2C3yHN7k_CkAVpl0fiJw.png 848w, https://cdn-images-1.medium.com/max/800/1*SC2C3yHN7k_CkAVpl0fiJw.png 1272w, https://cdn-images-1.medium.com/max/800/1*SC2C3yHN7k_CkAVpl0fiJw.png 1456w" sizes="100vw" fetchpriority="high"></picture><div></div></div></a></figure></div><p>I gave a talk at Copenhagen React about the <a href="https://trpc.io">tRPC</a> framework and how we use it instead of REST or GraphQL at <a href="https://mymee.com">Mymee</a>:</p><p>https://www.<a href="https://t.co/5Inn6mfPVP">youtube.com/watch?v=k1TCueEhhJo</a></p>]]></content:encoded></item><item><title><![CDATA[Negotiating? Make the First Offer.]]></title><description><![CDATA[I am writing this because I&#8217;ve found myself telling it verbally a couple of times by now so maybe it&#8217;s worth putting into text.]]></description><link>https://thoughts.kristiandupont.com/p/negotiating-make-the-first-offer-670b36b99966</link><guid isPermaLink="false">https://thoughts.kristiandupont.com/p/negotiating-make-the-first-offer-670b36b99966</guid><dc:creator><![CDATA[Kristian Dupont]]></dc:creator><pubDate>Wed, 29 Dec 2021 13:10:23 GMT</pubDate><enclosure url="https://substackcdn.com/image/youtube/w_728,c_limit/8ThGnoWBC94" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I am writing this because I&#8217;ve found myself telling it verbally a couple of times by now so maybe it&#8217;s worth putting into text.</p><p>It may just exist in my little bubble, but it seems that there is some conventional wisdom that says that in negotiations, you should try to get the other party make the first offer.</p><p>It&#8217;s not that I have researched this a whole lot. I listened to a podcast once with some guy who had negotiated deals for lots of big companies and was considered a heavy weight in the field. I remember being a bit annoyed with this because when you represent IBM in some deal, you almost certainly have what everyone knows is the real, deciding factor: leverage. At that point, I don&#8217;t really care about your theories on body language and whatever. I am sure he made some good points but none that left a lasting impression on me.</p><p>Being the one who makes the first offer, however, is something I have practiced for a long time and I feel it serves me well.</p><p>I can see two risks with it. One, you end up like Michael Jr. in Road to Perdition (sadly I couldn&#8217;t find this clip anywhere):</p><p>Michael Sullivan, Jr.:<br>&gt; So when do I get my share of the money?&nbsp;<br>Michael Sullivan:<br>&gt; Well&#8230; how much do you want?&nbsp;<br>Michael Sullivan, Jr.:<br>&gt; Two hundred dollars.&nbsp;<br>Michael Sullivan:<br>&gt; Okay. Deal.</p><p>&#8230;</p><p>Michael Sullivan, Jr.:<br>&gt; Could I have had more?&nbsp;<br>Michael Sullivan:<br>&gt; You&#8217;ll never know.</p><p>The second risk is that you suggest something so outrageous that you get an Al Swearengen-style response:</p><div class="captioned-image-container"><figure><div id="youtube2-8ThGnoWBC94" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;8ThGnoWBC94&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/8ThGnoWBC94?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div></figure></div><p>Now, regarding situation #1, how likely do you think the situation is where your new employer thinks to themself &#8220;Great! We were going to offer twice the salary&#8202;&#8212;&#8202;but now we can get better drinks at the next company party instead&#8221;? I imagine it&#8217;s probably quite unlikely. If you are completely unaware of the value of the product or service you are buying or selling, negotiation is not the time where you are going to discover it.</p><p>As for #2, well even if this is indeed how they respond, do you think you would then have been interested at the price point (or whatever is being negotiated) that they had in mind? It&#8217;s not a pleasant situation for sure, but it&#8217;s probably good to get it out of the way and you can all be on your way.</p><p>If you want the other party to offer first because you think of a negotiation as a sort of poker game and you think that them revealing their hand gives you an advantage, I just have to say that I don&#8217;t see what that would be. State what you would like and work from there. If the other party wants a different number, it&#8217;s now on them to convince and compel you to move and not the other way around.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!TKg1!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0e7814eb-01c6-4b6d-a99f-8542e14263f0_800x1200.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!TKg1!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0e7814eb-01c6-4b6d-a99f-8542e14263f0_800x1200.jpeg 424w, https://substackcdn.com/image/fetch/$s_!TKg1!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0e7814eb-01c6-4b6d-a99f-8542e14263f0_800x1200.jpeg 848w, https://substackcdn.com/image/fetch/$s_!TKg1!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0e7814eb-01c6-4b6d-a99f-8542e14263f0_800x1200.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!TKg1!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0e7814eb-01c6-4b6d-a99f-8542e14263f0_800x1200.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!TKg1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0e7814eb-01c6-4b6d-a99f-8542e14263f0_800x1200.jpeg" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0e7814eb-01c6-4b6d-a99f-8542e14263f0_800x1200.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:null,&quot;width&quot;:null,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!TKg1!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0e7814eb-01c6-4b6d-a99f-8542e14263f0_800x1200.jpeg 424w, https://substackcdn.com/image/fetch/$s_!TKg1!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0e7814eb-01c6-4b6d-a99f-8542e14263f0_800x1200.jpeg 848w, https://substackcdn.com/image/fetch/$s_!TKg1!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0e7814eb-01c6-4b6d-a99f-8542e14263f0_800x1200.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!TKg1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0e7814eb-01c6-4b6d-a99f-8542e14263f0_800x1200.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a><figcaption class="image-caption">Photo by <a href="https://unsplash.com/@charlesdeluvio?utm_source=medium&amp;utm_medium=referral">Charles Deluvio</a> on&nbsp;<a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure></div>]]></content:encoded></item><item><title><![CDATA[Podcast interview]]></title><description><![CDATA[I was interviewed by Nick Janetakis for the Running In Production Podcast:]]></description><link>https://thoughts.kristiandupont.com/p/podcast-interview-e91cf76ce3d9</link><guid isPermaLink="false">https://thoughts.kristiandupont.com/p/podcast-interview-e91cf76ce3d9</guid><dc:creator><![CDATA[Kristian Dupont]]></dc:creator><pubDate>Thu, 19 Aug 2021 17:14:53 GMT</pubDate><enclosure url="https://cdn-images-1.medium.com/max/800/0*nUOV3RpeKvc61M2D" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://cdn-images-1.medium.com/max/800/0*nUOV3RpeKvc61M2D" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://cdn-images-1.medium.com/max/800/0*nUOV3RpeKvc61M2D 424w, https://cdn-images-1.medium.com/max/800/0*nUOV3RpeKvc61M2D 848w, https://cdn-images-1.medium.com/max/800/0*nUOV3RpeKvc61M2D 1272w, https://cdn-images-1.medium.com/max/800/0*nUOV3RpeKvc61M2D 1456w" sizes="100vw"><img src="https://cdn-images-1.medium.com/max/800/0*nUOV3RpeKvc61M2D" data-attrs="{&quot;src&quot;:&quot;https://cdn-images-1.medium.com/max/800/0*nUOV3RpeKvc61M2D&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:null,&quot;width&quot;:null,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://cdn-images-1.medium.com/max/800/0*nUOV3RpeKvc61M2D 424w, https://cdn-images-1.medium.com/max/800/0*nUOV3RpeKvc61M2D 848w, https://cdn-images-1.medium.com/max/800/0*nUOV3RpeKvc61M2D 1272w, https://cdn-images-1.medium.com/max/800/0*nUOV3RpeKvc61M2D 1456w" sizes="100vw" fetchpriority="high"></picture><div></div></div></a><figcaption class="image-caption">Photo by <a href="https://unsplash.com/@jukkaaalho?utm_source=medium&amp;utm_medium=referral">Jukka Aalho</a> on&nbsp;<a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure></div><p>I was interviewed by Nick Janetakis for the Running In Production Podcast:</p><p><strong><a href="https://runninginproduction.com/podcast/95-submotion-helps-you-manage-access-control-for-your-saas-subscriptions" title="https://runninginproduction.com/podcast/95-submotion-helps-you-manage-access-control-for-your-saas-subscriptions">Submotion Helps You Manage Access Control for Your SAAS Subscriptions</a></strong><a href="https://runninginproduction.com/podcast/95-submotion-helps-you-manage-access-control-for-your-saas-subscriptions" title="https://runninginproduction.com/podcast/95-submotion-helps-you-manage-access-control-for-your-saas-subscriptions"><br></a><em><a href="https://runninginproduction.com/podcast/95-submotion-helps-you-manage-access-control-for-your-saas-subscriptions" title="https://runninginproduction.com/podcast/95-submotion-helps-you-manage-access-control-for-your-saas-subscriptions">In this episode of Running in Production, Kristian Dupont goes over building a SAAS app to manage access control to&#8230;</a></em><a href="https://runninginproduction.com/podcast/95-submotion-helps-you-manage-access-control-for-your-saas-subscriptions" title="https://runninginproduction.com/podcast/95-submotion-helps-you-manage-access-control-for-your-saas-subscriptions">runninginproduction.com</a></p><p>We get into quite a few details about the architectural choices I&#8217;ve made for Submotion and how it all works together.</p><p>I&#8217;ve written before about the way I let the Postgres schema act as the source of truth by generating types with <a href="https://github.com/kristiandupont/kanel">Kanel</a> (assisted by <a href="https://github.com/kristiandupont/schemalint">Schemalint</a>). In addition to this, I mention a homemade framework that sort of resembles a server-side Redux. This gives me typesafe database queries that are automatically reflected all the way to the frontend. I am frequently asked to make it open source and that is still the plan. I just need to untangle it from the Submotion source code which just never seems to be high enough priority. I promise I will get around to it&nbsp;:-)</p>]]></content:encoded></item><item><title><![CDATA[Testing in production: using JSON Schema for 3rd party API response validation]]></title><description><![CDATA[..ensuring sound mocks and supplying Typescript types.]]></description><link>https://thoughts.kristiandupont.com/p/testing-3rd-party-api-access-b2808494ccd</link><guid isPermaLink="false">https://thoughts.kristiandupont.com/p/testing-3rd-party-api-access-b2808494ccd</guid><dc:creator><![CDATA[Kristian Dupont]]></dc:creator><pubDate>Sun, 08 Aug 2021 07:20:47 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/525d8064-7901-4c8c-8c4e-9080a6d5318b_800x668.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote><p>..ensuring sound mocks and supplying Typescript types.</p></blockquote><p><a href="https://www.submotion.io/">Submotion</a> offers a central place to manage SaaS accounts and subscriptions. This is made possible by connecting to a bunch of third party API&#8217;s. It&#8217;s an amazing fact that so many companies expose API&#8217;s that allow you to integrate with their service, to mutual benefit. Using such API&#8217;s is fairly trivial and even though things like OAuth implementations quite often diverge from the standard, it&#8217;s usually not too hard to set up.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!BXoi!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f568d71-2b82-422d-a7a9-65864c68330a_800x668.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!BXoi!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f568d71-2b82-422d-a7a9-65864c68330a_800x668.png 424w, https://substackcdn.com/image/fetch/$s_!BXoi!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f568d71-2b82-422d-a7a9-65864c68330a_800x668.png 848w, https://substackcdn.com/image/fetch/$s_!BXoi!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f568d71-2b82-422d-a7a9-65864c68330a_800x668.png 1272w, https://substackcdn.com/image/fetch/$s_!BXoi!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f568d71-2b82-422d-a7a9-65864c68330a_800x668.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!BXoi!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f568d71-2b82-422d-a7a9-65864c68330a_800x668.png" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0f568d71-2b82-422d-a7a9-65864c68330a_800x668.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:null,&quot;width&quot;:null,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!BXoi!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f568d71-2b82-422d-a7a9-65864c68330a_800x668.png 424w, https://substackcdn.com/image/fetch/$s_!BXoi!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f568d71-2b82-422d-a7a9-65864c68330a_800x668.png 848w, https://substackcdn.com/image/fetch/$s_!BXoi!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f568d71-2b82-422d-a7a9-65864c68330a_800x668.png 1272w, https://substackcdn.com/image/fetch/$s_!BXoi!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f568d71-2b82-422d-a7a9-65864c68330a_800x668.png 1456w" sizes="100vw" fetchpriority="high"></picture><div></div></div></a></figure></div><p>However<em>, testing</em> such integrations is painful.</p><p>Proper end-to-end testing is not feasible in a CI setup. It&#8217;s not only slow and relies on access to outside servers that could be down, it&#8217;s also next to impossible to set up useful fixtures.</p><p>Initially I tried to set up a record/playback system to alleviate this. That works well in some situations, but Submotion almost always connects via OAuth which is difficult to automate (by design), and even then, I still had the issue with fixtures. I would want to test that Submotion detects, say, that an account has been deleted. In order to do that, I had to actually create and subsequently delete an account in Slack or whatever, a very involved task.&nbsp;<br>So I dropped that approach and for a while I just accepted that mocks were the best I could do. And in fact, I am still writing manual mocks, but I did manage to improve the situation.</p><p>I was discussing this with my friend Lars who has put a lot of thought into these and <a href="https://www.fullstackagile.eu/2016/03/20/dont-let-your-mocks-lie-to-you/">similar</a> <a href="https://www.fullstackagile.eu/2021/06/24/bluetooth-ble-mock-recorder/">issues</a>. He suggested that I <em>move the assertions from the test into production code</em>. Now, I remember doing this sort of thing a lot in C++ where assertions are common in the debug build and then removed in production. However, this didn&#8217;t feel like a Node-native approach and it rubbed me the wrong way somehow. But, as he pointed out, such assertions could just log warnings in production, and when running locally or via tests, they could throw. That way, I would continuously run validation on real responses meaning that said validations are kept up to date and good enough to keep my mock data sound. Although this is strictly validating, it still feels to me like a variation on the <a href="https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/">parse, don&#8217;t validate</a> philosophy coined by Alexis King. And while it&#8217;s still work in progress, so far it&#8217;s a big step forward.</p><p>Here is what I do in practice:</p><ol><li><p>Make a request or two to the 3rd party API in question, and save the JSON responses in a file. This is the sketch.</p></li><li><p>Convert these into JSON Schemas using a tool like <a href="https://transform.tools/json-to-json-schema">https://transform.tools/json-to-json-schema</a></p></li><li><p>Clean up the schemas. They can rarely be inferred correctly from a single response, so some tweaking is required. Notably, I add <a href="https://json-schema.org/understanding-json-schema/reference/string.html#format"><code>format</code></a> fields for things like emails, URLs or IP addresses where applicable.</p></li><li><p>Then, use <a href="https://ajv.js.org/">AJV</a> (with <a href="https://www.npmjs.com/package/ajv-formats">ajv-formats</a>) to validate the responses right after making a request. If validation fails, log an error or, if running tests, throw an exception</p></li><li><p>Finally, use <a href="https://www.npmjs.com/package/as-typed">as-typed</a> with the schema to get a typed response. It&#8217;s not perfect but much nicer than an untyped one and I can be confident that my runtime validation is aligned with the type check.</p></li></ol><p>Now, I could replace JSON Schema validation with something like <a href="https://gcanti.github.io/io-ts/">io-ts</a> or <a href="https://github.com/colinhacks/zod">Zod</a> which I am playing with, but this is what I am doing for now. Here is an example and a simplified version of the approach:</p><p>Of course, schema validation/type checking only looks at the shape of the responses which means there is still lots of room for semantic errors. There is nothing stopping me from expanding this setup with more semantic assertions which would further improve the quality but I haven&#8217;t really found my sweet spot for that yet. Perhaps in a future post.</p>]]></content:encoded></item></channel></rss>